(BWDIC!) *user*: enforce it is not empty
[s-mailx.git] / nam-a-grp.c
blob6521a44c484d0a0f2620b1b83aa6e94fda19f6e5
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 (!file, !pipe) 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) &&
277 !(p->n_flags & (((ui32_t)SI32_MIN) | NAME_ADDRSPEC_ISFILE |
278 NAME_ADDRSPEC_ISPIPE)) &&
279 a_nag_is_same_name(p->n_name, name))
280 p->n_flags |= (ui32_t)SI32_MIN;
281 NYD2_LEAVE;
282 return np;
285 static char const *
286 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
287 int keepcomms)
289 char const *cp;
290 char *wp, c, inquote, lc, lastsp;
291 NYD_ENTER;
293 *(wp = wbuf) = '\0';
295 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
296 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
298 if (c == '\0') {
299 cp = NULL;
300 goto jleave;
303 /* Parse a full name: TODO RFC 5322
304 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
305 * - Skip entire (nested) comments
306 * - In non-quote, non-comment, join adjacent space to a single SP
307 * - Understand separators only in non-quote, non-comment context,
308 * and only if not part of a *quoted-pair* (XXX too liberal) */
309 cp = ap;
310 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
311 c = *cp;
312 if (c == '\0')
313 break;
314 if (c == '\\')
315 goto jwpwc;
316 if (c == '"') {
317 if (lc != '\\')
318 inquote = !inquote;
319 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
320 else
321 --wp;
322 #endif
323 goto jwpwc;
325 if (inquote || lc == '\\') {
326 jwpwc:
327 *wp++ = c;
328 lastsp = 0;
329 continue;
331 if (c == '(') {
332 ap = cp;
333 cp = skip_comment(cp + 1);
334 if (keepcomms)
335 while (ap < cp)
336 *wp++ = *ap++;
337 --cp;
338 lastsp = 0;
339 continue;
341 if (strchr(separators, c) != NULL)
342 break;
344 lc = lastsp;
345 lastsp = blankchar(c);
346 if (!lastsp || !lc)
347 *wp++ = c;
349 if (blankchar(lc))
350 --wp;
352 *wp = '\0';
353 jleave:
354 NYD_LEAVE;
355 return cp;
358 static struct name *
359 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
360 bool_t keepcomms)
362 struct name *topp, *np, *t;
363 char const *cp;
364 char *nbuf;
365 NYD_ENTER;
367 topp = NULL;
368 if (line == NULL || *line == '\0')
369 goto jleave;
371 np = NULL;
372 cp = line;
373 nbuf = n_alloc(strlen(line) +1);
374 while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
375 t = nalloc(nbuf, ntype);
376 if (topp == NULL)
377 topp = t;
378 else
379 np->n_flink = t;
380 t->n_blink = np;
381 np = t;
383 n_free(nbuf);
384 jleave:
385 NYD_LEAVE;
386 return topp;
389 static struct name *
390 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
391 bool_t metoo, int ntype){
392 struct a_nag_grp_names *ngnp;
393 struct name *nlist_tail;
394 char const *logname;
395 struct a_nag_grp_names_head *ngnhp;
396 NYD2_ENTER;
398 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
399 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
400 goto jleave;
403 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
404 logname = ok_vlook(LOGNAME);
406 for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
407 struct a_nag_group *xngp;
408 char *cp;
410 cp = ngnp->ngn_id;
412 if(!strcmp(cp, ngp->ng_id))
413 goto jas_is;
415 if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
416 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
417 * to a full address specification; aliases cannot be empty */
418 struct a_nag_grp_names_head *xngnhp;
420 a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
422 assert(xngnhp->ngnh_head != NULL);
423 if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
424 !a_nag_is_same_name(cp, logname))
425 nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
426 continue;
429 /* Here we should allow to expand to itself if only person in alias */
430 jas_is:
431 if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
432 !a_nag_is_same_name(cp, logname)){
433 struct name *np;
435 np = nalloc(cp, ntype | GFULL);
436 if((nlist_tail = nlist) != NULL){
437 while(nlist_tail->n_flink != NULL)
438 nlist_tail = nlist_tail->n_flink;
439 nlist_tail->n_flink = np;
440 np->n_blink = nlist_tail;
441 }else
442 nlist = np;
445 jleave:
446 NYD2_LEAVE;
447 return nlist;
450 static int
451 a_nag_elide_qsort(void const *s1, void const *s2){
452 struct name const * const *np1, * const *np2;
453 int rv;
454 NYD2_ENTER;
456 np1 = s1;
457 np2 = s2;
458 if(!(rv = asccasecmp((*np1)->n_name, (*np2)->n_name))){
459 n_LCTAV(GTO < GCC && GCC < GBCC);
460 rv = ((*np1)->n_type & (GTO | GCC | GBCC)) -
461 ((*np2)->n_type & (GTO | GCC | GBCC));
463 NYD2_LEAVE;
464 return rv;
467 static struct a_nag_group *
468 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
469 char const *id){
470 char c1;
471 struct a_nag_group *lngp, *ngp;
472 bool_t icase;
473 NYD2_ENTER;
475 icase = FAL0;
477 /* C99 */{
478 ui32_t h;
479 struct a_nag_group **ngpa;
481 switch((nt &= a_NAG_T_MASK)){
482 case a_NAG_T_ALTERNATES:
483 ngpa = a_nag_alternates_heads;
484 icase = TRU1;
485 break;
486 default:
487 case a_NAG_T_COMMANDALIAS:
488 ngpa = a_nag_commandalias_heads;
489 break;
490 case a_NAG_T_ALIAS:
491 ngpa = a_nag_alias_heads;
492 break;
493 case a_NAG_T_MLIST:
494 ngpa = a_nag_mlist_heads;
495 icase = TRU1;
496 break;
497 case a_NAG_T_SHORTCUT:
498 ngpa = a_nag_shortcut_heads;
499 break;
500 case a_NAG_T_CHARSETALIAS:
501 ngpa = a_nag_charsetalias_heads;
502 icase = TRU1;
503 break;
504 case a_NAG_T_FILETYPE:
505 ngpa = a_nag_filetype_heads;
506 icase = TRU1;
507 break;
510 nglp->ngl_htable = ngpa;
511 h = icase ? n_torek_ihash(id) : n_torek_hash(id);
512 ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
515 lngp = NULL;
516 c1 = *id++;
518 if(icase){
519 c1 = lowerconv(c1);
520 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
521 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
522 !asccasecmp(&ngp->ng_id[1], id))
523 break;
524 }else{
525 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
526 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
527 !strcmp(&ngp->ng_id[1], id))
528 break;
531 nglp->ngl_slot_last = lngp;
532 nglp->ngl_group = ngp;
533 NYD2_LEAVE;
534 return ngp;
537 static struct a_nag_group *
538 a_nag_group_find(enum a_nag_type nt, char const *id){
539 struct a_nag_group_lookup ngl;
540 struct a_nag_group *ngp;
541 NYD2_ENTER;
543 ngp = a_nag_group_lookup(nt, &ngl, id);
544 NYD2_LEAVE;
545 return ngp;
548 static struct a_nag_group *
549 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
550 size_t i;
551 struct a_nag_group **ngpa, *ngp;
552 NYD2_ENTER;
554 switch((nt &= a_NAG_T_MASK)){
555 case a_NAG_T_ALTERNATES:
556 ngpa = a_nag_alternates_heads;
557 break;
558 default:
559 case a_NAG_T_COMMANDALIAS:
560 ngpa = a_nag_commandalias_heads;
561 break;
562 case a_NAG_T_ALIAS:
563 ngpa = a_nag_alias_heads;
564 break;
565 case a_NAG_T_MLIST:
566 ngpa = a_nag_mlist_heads;
567 break;
568 case a_NAG_T_SHORTCUT:
569 ngpa = a_nag_shortcut_heads;
570 break;
571 case a_NAG_T_CHARSETALIAS:
572 ngpa = a_nag_charsetalias_heads;
573 break;
574 case a_NAG_T_FILETYPE:
575 ngpa = a_nag_filetype_heads;
576 break;
579 nglp->ngl_htable = ngpa;
581 for(i = 0; i < HSHSIZE; ++ngpa, ++i)
582 if((ngp = *ngpa) != NULL){
583 nglp->ngl_slot = ngpa;
584 nglp->ngl_group = ngp;
585 goto jleave;
588 nglp->ngl_group = ngp = NULL;
589 jleave:
590 nglp->ngl_slot_last = NULL;
591 NYD2_LEAVE;
592 return ngp;
595 static struct a_nag_group *
596 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
597 struct a_nag_group *ngp, **ngpa;
598 NYD2_ENTER;
600 if((ngp = nglp->ngl_group->ng_next) != NULL)
601 nglp->ngl_slot_last = nglp->ngl_group;
602 else{
603 nglp->ngl_slot_last = NULL;
604 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
605 if((ngp = *nglp->ngl_slot) != NULL)
606 break;
608 nglp->ngl_group = ngp;
609 NYD2_LEAVE;
610 return ngp;
613 static struct a_nag_group *
614 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
615 struct a_nag_group_lookup ngl;
616 struct a_nag_group *ngp;
617 size_t l, i;
618 NYD2_ENTER;
620 if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
621 goto jleave;
623 l = strlen(id) +1;
624 if(UIZ_MAX - n_ALIGN(l) <=
625 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
626 goto jleave;
628 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
629 switch(nt & a_NAG_T_MASK){
630 case a_NAG_T_ALTERNATES:
631 case a_NAG_T_SHORTCUT:
632 case a_NAG_T_CHARSETALIAS:
633 default:
634 break;
635 case a_NAG_T_COMMANDALIAS:
636 addsz += sizeof(struct a_nag_cmd_alias);
637 break;
638 case a_NAG_T_ALIAS:
639 addsz += sizeof(struct a_nag_grp_names_head);
640 break;
641 case a_NAG_T_MLIST:
642 #ifdef HAVE_REGEX
643 if(n_is_maybe_regex(id)){
644 addsz = sizeof(struct a_nag_grp_regex);
645 nt |= a_NAG_T_REGEX;
647 #endif
648 break;
649 case a_NAG_T_FILETYPE:
650 addsz += sizeof(struct a_nag_file_type);
651 break;
653 if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
654 goto jleave;
656 ngp = n_alloc(i + addsz);
657 memcpy(ngp->ng_id, id, l);
658 ngp->ng_subclass_off = (ui32_t)i;
659 ngp->ng_id_len_sub = (ui16_t)(i - --l);
660 ngp->ng_type = nt;
661 switch(nt & a_NAG_T_MASK){
662 case a_NAG_T_ALTERNATES:
663 case a_NAG_T_MLIST:
664 case a_NAG_T_CHARSETALIAS:
665 case a_NAG_T_FILETYPE:{
666 char *cp, c;
668 for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
669 *cp = lowerconv(c);
670 }break;
671 default:
672 break;
675 if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
676 struct a_nag_grp_names_head *ngnhp;
678 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
679 ngnhp->ngnh_head = NULL;
681 #ifdef HAVE_REGEX
682 else if(nt & a_NAG_T_REGEX){
683 int s;
684 struct a_nag_grp_regex *ngrp;
686 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
688 if((s = regcomp(&ngrp->ngr_regex, id,
689 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
690 n_err(_("Invalid regular expression: %s: %s\n"),
691 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(NULL, s));
692 n_free(ngp);
693 ngp = NULL;
694 goto jleave;
696 ngrp->ngr_mygroup = ngp;
697 a_nag_mlmux_linkin(ngp);
699 #endif /* HAVE_REGEX */
701 ngp->ng_next = *ngl.ngl_slot;
702 *ngl.ngl_slot = ngp;
703 jleave:
704 NYD2_LEAVE;
705 return ngp;
708 static bool_t
709 a_nag_group_del(enum a_nag_type nt, char const *id){
710 struct a_nag_group_lookup ngl;
711 struct a_nag_group *ngp;
712 enum a_nag_type xnt;
713 NYD2_ENTER;
715 xnt = nt & a_NAG_T_MASK;
717 /* Delete 'em all? */
718 if(id[0] == '*' && id[1] == '\0'){
719 for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
720 ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
721 : a_nag_group_go_next(&ngl);
722 ngp = (struct a_nag_group*)TRU1;
723 }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
724 if(ngp->ng_type & xnt)
725 a_nag__group_del(&ngl);
726 else
727 ngp = NULL;
729 NYD2_LEAVE;
730 return (ngp != NULL);
733 static struct a_nag_group *
734 a_nag__group_del(struct a_nag_group_lookup *nglp){
735 struct a_nag_group *x, *ngp;
736 NYD2_ENTER;
738 /* Overly complicated: link off this node, step ahead to next.. */
739 x = nglp->ngl_group;
740 if((ngp = nglp->ngl_slot_last) != NULL)
741 ngp = (ngp->ng_next = x->ng_next);
742 else{
743 nglp->ngl_slot_last = NULL;
744 ngp = (*nglp->ngl_slot = x->ng_next);
746 if(ngp == NULL){
747 struct a_nag_group **ngpa;
749 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
750 if((ngp = *nglp->ngl_slot) != NULL)
751 break;
754 nglp->ngl_group = ngp;
756 if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
757 a_nag__names_del(x);
758 #ifdef HAVE_REGEX
759 else if(x->ng_type & a_NAG_T_REGEX){
760 struct a_nag_grp_regex *ngrp;
762 a_NAG_GP_TO_SUBCLASS(ngrp, x);
764 regfree(&ngrp->ngr_regex);
765 a_nag_mlmux_linkout(x);
767 #endif
769 n_free(x);
770 NYD2_LEAVE;
771 return ngp;
774 static void
775 a_nag__names_del(struct a_nag_group *ngp){
776 struct a_nag_grp_names_head *ngnhp;
777 struct a_nag_grp_names *ngnp;
778 NYD2_ENTER;
780 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
782 for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
783 struct a_nag_grp_names *x;
785 x = ngnp;
786 ngnp = ngnp->ngn_next;
787 n_free(x);
789 NYD2_LEAVE;
792 static bool_t
793 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
794 struct n_string s;
795 size_t lines;
796 FILE *fp;
797 char const **ida;
798 struct a_nag_group const *ngp;
799 ui32_t h, i;
800 struct a_nag_group **ngpa;
801 char const *tname;
802 enum a_nag_type xnt;
803 NYD_ENTER;
805 if(varname != NULL)
806 n_string_creat_auto(&s);
808 xnt = nt & a_NAG_T_PRINT_MASK;
810 switch(xnt & a_NAG_T_MASK){
811 case a_NAG_T_ALTERNATES:
812 tname = "alternates";
813 ngpa = a_nag_alternates_heads;
814 break;
815 default:
816 case a_NAG_T_COMMANDALIAS:
817 tname = "commandalias";
818 ngpa = a_nag_commandalias_heads;
819 break;
820 case a_NAG_T_ALIAS:
821 tname = "alias";
822 ngpa = a_nag_alias_heads;
823 break;
824 case a_NAG_T_MLIST:
825 tname = "mlist";
826 ngpa = a_nag_mlist_heads;
827 break;
828 case a_NAG_T_SHORTCUT:
829 tname = "shortcut";
830 ngpa = a_nag_shortcut_heads;
831 break;
832 case a_NAG_T_CHARSETALIAS:
833 tname = "charsetalias";
834 ngpa = a_nag_charsetalias_heads;
835 break;
836 case a_NAG_T_FILETYPE:
837 tname = "filetype";
838 ngpa = a_nag_filetype_heads;
839 break;
842 /* Count entries */
843 for(i = h = 0; h < HSHSIZE; ++h)
844 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
845 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
846 ++i;
847 if(i == 0){
848 if(varname == NULL)
849 fprintf(n_stdout, _("# no %s registered\n"), tname);
850 goto jleave;
852 ++i;
853 ida = n_autorec_alloc(i * sizeof *ida);
855 /* Create alpha sorted array of entries */
856 for(i = h = 0; h < HSHSIZE; ++h)
857 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
858 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
859 ida[i++] = ngp->ng_id;
860 if(i > 1)
861 qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
862 ida[i] = NULL;
864 if(varname != NULL)
865 fp = NULL;
866 else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
867 ) == NULL)
868 fp = n_stdout;
870 /* Create visual result */
871 lines = 0;
873 switch(xnt & a_NAG_T_MASK){
874 case a_NAG_T_ALTERNATES:
875 if(fp != NULL){
876 fputs(tname, fp);
877 lines = 1;
879 break;
880 default:
881 break;
884 for(i = 0; ida[i] != NULL; ++i)
885 lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
887 #ifdef HAVE_REGEX
888 if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
889 if(nt & a_NAG_T_SUBSCRIBE)
890 i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
891 else
892 i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
894 if(i > 0 && (n_poption & n_PO_D_V)){
895 assert(fp != NULL);
896 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
897 (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
898 i, h);
899 ++lines;
902 #endif
904 switch(xnt & a_NAG_T_MASK){
905 case a_NAG_T_ALTERNATES:
906 if(fp != NULL){
907 putc('\n', fp);
908 assert(lines == 1);
910 break;
911 default:
912 break;
915 if(varname == NULL && fp != n_stdout){
916 assert(fp != NULL);
917 page_or_print(fp, lines);
918 Fclose(fp);
921 jleave:
922 if(varname != NULL){
923 tname = n_string_cp(&s);
924 if(n_var_vset(varname, (uintptr_t)tname))
925 varname = NULL;
926 else
927 n_pstate_err_no = n_ERR_NOTSUP;
929 NYD_LEAVE;
930 return (varname == NULL);
933 static int
934 a_nag__group_print_qsorter(void const *a, void const *b){
935 int rv;
936 NYD2_ENTER;
938 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
939 NYD2_LEAVE;
940 return rv;
943 static size_t
944 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
945 struct n_string *vputsp){
946 char const *cp;
947 size_t rv;
948 NYD2_ENTER;
950 rv = 1;
952 switch(ngp->ng_type & a_NAG_T_MASK){
953 case a_NAG_T_ALTERNATES:{
954 if(fo != NULL)
955 fprintf(fo, " %s", ngp->ng_id);
956 else{
957 if(vputsp->s_len > 0)
958 vputsp = n_string_push_c(vputsp, ' ');
959 /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
961 rv = 0;
962 }break;
963 case a_NAG_T_COMMANDALIAS:{
964 struct a_nag_cmd_alias *ncap;
966 assert(fo != NULL); /* xxx no vput yet */
967 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
968 fprintf(fo, "commandalias %s %s\n",
969 n_shexp_quote_cp(ngp->ng_id, TRU1),
970 n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
971 }break;
972 case a_NAG_T_ALIAS:{
973 struct a_nag_grp_names_head *ngnhp;
974 struct a_nag_grp_names *ngnp;
976 assert(fo != NULL); /* xxx no vput yet */
977 fprintf(fo, "alias %s ", ngp->ng_id);
979 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
980 if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
982 struct a_nag_grp_names *x;
984 x = ngnp;
985 ngnp = ngnp->ngn_next;
986 fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
987 }while(ngnp != NULL);
989 putc('\n', fo);
990 }break;
991 case a_NAG_T_MLIST:
992 assert(fo != NULL); /* xxx no vput yet */
993 #ifdef HAVE_REGEX
994 if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
995 size_t i;
996 struct a_nag_grp_regex *lp, *ngrp;
998 lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
999 : a_nag_mlist_regex);
1000 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1001 for(i = 1; lp != ngrp; lp = lp->ngr_next)
1002 ++i;
1003 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
1004 ngrp->ngr_hits, i);
1005 ++rv;
1007 #endif
1008 fprintf(fo, "wysh %s %s\n",
1009 (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
1010 n_shexp_quote_cp(ngp->ng_id, TRU1));
1011 break;
1012 case a_NAG_T_SHORTCUT:
1013 assert(fo != NULL); /* xxx no vput yet */
1014 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1015 fprintf(fo, "wysh shortcut %s %s\n",
1016 ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
1017 break;
1018 case a_NAG_T_CHARSETALIAS:
1019 assert(fo != NULL); /* xxx no vput yet */
1020 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1021 fprintf(fo, "charsetalias %s %s\n",
1022 n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
1023 break;
1024 case a_NAG_T_FILETYPE:{
1025 struct a_nag_file_type *nftp;
1027 assert(fo != NULL); /* xxx no vput yet */
1028 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
1029 fprintf(fo, "filetype %s %s %s\n",
1030 n_shexp_quote_cp(ngp->ng_id, TRU1),
1031 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
1032 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
1033 }break;
1035 NYD2_LEAVE;
1036 return rv;
1039 static int
1040 a_nag_mlmux(enum a_nag_type nt, char const **argv){
1041 struct a_nag_group *ngp;
1042 char const *ecp;
1043 int rv;
1044 NYD2_ENTER;
1046 rv = 0;
1047 n_UNINIT(ecp, NULL);
1049 if(*argv == NULL)
1050 a_nag_group_print_all(nt, NULL);
1051 else do{
1052 if((ngp = a_nag_group_find(nt, *argv)) != NULL){
1053 if(nt & a_NAG_T_SUBSCRIBE){
1054 if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
1055 a_NAG_MLMUX_LINKOUT(ngp);
1056 ngp->ng_type |= a_NAG_T_SUBSCRIBE;
1057 a_NAG_MLMUX_LINKIN(ngp);
1058 }else{
1059 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
1060 goto jerr;
1062 }else{
1063 ecp = N_("Mailing-list already `mlist'ed: %s\n");
1064 goto jerr;
1066 }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
1067 ecp = N_("Failed to create storage for mailing-list: %s\n");
1068 jerr:
1069 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1070 rv = 1;
1072 }while(*++argv != NULL);
1074 NYD2_LEAVE;
1075 return rv;
1078 static int
1079 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
1080 struct a_nag_group *ngp;
1081 int rv;
1082 NYD2_ENTER;
1084 rv = 0;
1086 for(; *argv != NULL; ++argv){
1087 if(nt & a_NAG_T_SUBSCRIBE){
1088 struct a_nag_group_lookup ngl;
1089 bool_t isaster;
1091 if(!(isaster = (**argv == '*')))
1092 ngp = a_nag_group_find(nt, *argv);
1093 else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
1094 continue;
1095 else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1096 goto jaster_entry;
1098 if(ngp != NULL){
1099 jaster_redo:
1100 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1101 a_NAG_MLMUX_LINKOUT(ngp);
1102 ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
1103 a_NAG_MLMUX_LINKIN(ngp);
1105 if(isaster){
1106 jaster_entry:
1107 while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
1108 !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1110 if(ngp != NULL)
1111 goto jaster_redo;
1113 }else{
1114 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1115 n_shexp_quote_cp(*argv, FAL0));
1116 rv = 1;
1118 continue;
1120 }else if(a_nag_group_del(nt, *argv))
1121 continue;
1122 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
1123 rv = 1;
1125 NYD2_LEAVE;
1126 return rv;
1129 #ifdef HAVE_REGEX
1130 static void
1131 a_nag_mlmux_linkin(struct a_nag_group *ngp){
1132 struct a_nag_grp_regex **lpp, *ngrp, *lhp;
1133 NYD2_ENTER;
1135 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1136 lpp = &a_nag_mlsub_regex;
1137 ++a_nag_mlsub_size;
1138 }else{
1139 lpp = &a_nag_mlist_regex;
1140 ++a_nag_mlist_size;
1143 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1145 if((lhp = *lpp) != NULL){
1146 (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
1147 (ngrp->ngr_next = lhp)->ngr_last = ngrp;
1148 }else
1149 *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
1150 ngrp->ngr_hits = 0;
1151 NYD2_LEAVE;
1154 static void
1155 a_nag_mlmux_linkout(struct a_nag_group *ngp){
1156 struct a_nag_grp_regex *ngrp, **lpp;
1157 NYD2_ENTER;
1159 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1161 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1162 lpp = &a_nag_mlsub_regex;
1163 --a_nag_mlsub_size;
1164 a_nag_mlsub_hits -= ngrp->ngr_hits;
1165 }else{
1166 lpp = &a_nag_mlist_regex;
1167 --a_nag_mlist_size;
1168 a_nag_mlist_hits -= ngrp->ngr_hits;
1171 if(ngrp->ngr_next == ngrp)
1172 *lpp = NULL;
1173 else{
1174 (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
1175 if(*lpp == ngrp)
1176 *lpp = ngrp->ngr_next;
1178 NYD2_LEAVE;
1180 #endif /* HAVE_REGEX */
1182 FL struct name *
1183 nalloc(char const *str, enum gfield ntype)
1185 struct n_addrguts ag;
1186 struct str in, out;
1187 struct name *np;
1188 NYD_ENTER;
1189 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1191 str = n_addrspec_with_guts(&ag, str,
1192 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1193 if(str == NULL){
1195 np = NULL; TODO We cannot return NULL,
1196 goto jleave; TODO thus handle failures in here!
1198 str = ag.ag_input;
1201 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1202 ag.ag_n_flags |= NAME_NAME_SALLOC;
1203 np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
1204 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1205 ag.ag_skinned = (char*)(np + 1);
1206 } else
1207 np = n_autorec_alloc(sizeof *np);
1209 np->n_flink = NULL;
1210 np->n_blink = NULL;
1211 np->n_type = ntype;
1212 np->n_fullname = np->n_name = ag.ag_skinned;
1213 np->n_fullextra = NULL;
1214 np->n_flags = ag.ag_n_flags;
1216 if (ntype & GFULL) {
1217 if (ag.ag_ilen == ag.ag_slen
1218 #ifdef HAVE_IDNA
1219 && !(ag.ag_n_flags & NAME_IDNA)
1220 #endif
1222 goto jleave;
1223 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1224 goto jleave;
1226 /* n_fullextra is only the complete name part without address.
1227 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1228 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1229 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1230 char const *cp;
1232 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1233 goto jskipfullextra;
1234 i = ag.ag_ilen - e;
1235 in.s = n_lofi_alloc(s + 1 + i +1);
1236 while(s > 0 && blankchar(str[s - 1]))
1237 --s;
1238 memcpy(in.s, str, s);
1239 if (i > 0) {
1240 in.s[s++] = ' ';
1241 while (blankchar(str[e])) {
1242 ++e;
1243 if (--i == 0)
1244 break;
1246 if (i > 0)
1247 memcpy(&in.s[s], &str[e], i);
1249 s += i;
1250 in.s[in.l = s] = '\0';
1251 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1253 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1255 while (i > 0 && spacechar(cp[i - 1]))
1256 --i;
1257 np->n_fullextra = savestrbuf(cp, i);
1259 n_lofi_free(in.s);
1260 n_free(out.s);
1262 jskipfullextra:
1264 /* n_fullname depends on IDNA conversion */
1265 #ifdef HAVE_IDNA
1266 if (!(ag.ag_n_flags & NAME_IDNA)) {
1267 #endif
1268 in.s = n_UNCONST(str);
1269 in.l = ag.ag_ilen;
1270 #ifdef HAVE_IDNA
1271 } else {
1272 /* The domain name was IDNA and has been converted. We also have to
1273 * ensure that the domain name in .n_fullname is replaced with the
1274 * converted version, since MIME doesn't perform encoding of addrs */
1275 /* TODO This definetely doesn't belong here! */
1276 size_t l = ag.ag_iaddr_start,
1277 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1278 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1279 memcpy(in.s, str, l);
1280 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1281 l += ag.ag_slen;
1282 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1283 l += lsuff;
1284 in.s[l] = '\0';
1285 in.l = l;
1287 #endif
1288 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1289 np->n_fullname = savestr(out.s);
1290 n_free(out.s);
1291 #ifdef HAVE_IDNA
1292 if (ag.ag_n_flags & NAME_IDNA)
1293 n_lofi_free(in.s);
1294 #endif
1296 jleave:
1297 NYD_LEAVE;
1298 return np;
1301 FL struct name *
1302 nalloc_fcc(char const *file){
1303 struct name *nnp;
1304 NYD_ENTER;
1306 nnp = n_autorec_alloc(sizeof *nnp);
1307 nnp->n_flink = nnp->n_blink = NULL;
1308 nnp->n_type = GBCC | GBCC_IS_FCC; /* xxx Bcc: <- namelist_vaporise_head */
1309 nnp->n_flags = NAME_NAME_SALLOC | NAME_SKINNED | NAME_ADDRSPEC_ISFILE;
1310 nnp->n_fullname = nnp->n_name = savestr(file);
1311 nnp->n_fullextra = NULL;
1312 NYD_LEAVE;
1313 return nnp;
1316 FL struct name *
1317 ndup(struct name *np, enum gfield ntype)
1319 struct name *nnp;
1320 NYD_ENTER;
1322 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1323 nnp = nalloc(np->n_name, ntype);
1324 goto jleave;
1327 nnp = n_autorec_alloc(sizeof *np);
1328 nnp->n_flink = nnp->n_blink = NULL;
1329 nnp->n_type = ntype;
1330 nnp->n_flags = np->n_flags | NAME_NAME_SALLOC;
1331 nnp->n_name = savestr(np->n_name);
1332 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1333 nnp->n_fullname = nnp->n_name;
1334 nnp->n_fullextra = NULL;
1335 } else {
1336 nnp->n_fullname = savestr(np->n_fullname);
1337 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1338 : savestr(np->n_fullextra);
1340 jleave:
1341 NYD_LEAVE;
1342 return nnp;
1345 FL struct name *
1346 cat(struct name *n1, struct name *n2){
1347 struct name *tail;
1348 NYD2_ENTER;
1350 tail = n2;
1351 if(n1 == NULL)
1352 goto jleave;
1353 tail = n1;
1354 if(n2 == NULL || (n2->n_type & GDEL))
1355 goto jleave;
1357 while(tail->n_flink != NULL)
1358 tail = tail->n_flink;
1359 tail->n_flink = n2;
1360 n2->n_blink = tail;
1361 tail = n1;
1362 jleave:
1363 NYD2_LEAVE;
1364 return tail;
1367 FL struct name *
1368 namelist_dup(struct name const *np, enum gfield ntype){
1369 struct name *nlist, *xnp;
1370 NYD2_ENTER;
1372 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1373 struct name *x;
1375 if(!(np->n_type & GDEL)){
1376 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1377 if((x->n_blink = xnp) == NULL)
1378 nlist = x;
1379 else
1380 xnp->n_flink = x;
1381 xnp = x;
1384 NYD2_LEAVE;
1385 return nlist;
1388 FL ui32_t
1389 count(struct name const *np)
1391 ui32_t c;
1392 NYD_ENTER;
1394 for (c = 0; np != NULL; np = np->n_flink)
1395 if (!(np->n_type & GDEL))
1396 ++c;
1397 NYD_LEAVE;
1398 return c;
1401 FL ui32_t
1402 count_nonlocal(struct name const *np)
1404 ui32_t c;
1405 NYD_ENTER;
1407 for (c = 0; np != NULL; np = np->n_flink)
1408 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1409 ++c;
1410 NYD_LEAVE;
1411 return c;
1414 FL struct name *
1415 extract(char const *line, enum gfield ntype)
1417 struct name *rv;
1418 NYD_ENTER;
1420 rv = a_nag_extract1(line, ntype, " \t,", 0);
1421 NYD_LEAVE;
1422 return rv;
1425 FL struct name *
1426 lextract(char const *line, enum gfield ntype)
1428 struct name *rv;
1429 NYD_ENTER;
1431 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1432 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1433 NYD_LEAVE;
1434 return rv;
1437 FL char *
1438 detract(struct name *np, enum gfield ntype)
1440 char *topp, *cp;
1441 struct name *p;
1442 int flags, s;
1443 NYD_ENTER;
1445 topp = NULL;
1446 if (np == NULL)
1447 goto jleave;
1449 flags = ntype & (GCOMMA | GNAMEONLY);
1450 ntype &= ~(GCOMMA | GNAMEONLY);
1451 s = 0;
1453 for (p = np; p != NULL; p = p->n_flink) {
1454 if (ntype && (p->n_type & GMASK) != ntype)
1455 continue;
1456 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1457 if (flags & GCOMMA)
1458 ++s;
1460 if (s == 0)
1461 goto jleave;
1463 s += 2;
1464 topp = n_autorec_alloc(s);
1465 cp = topp;
1466 for (p = np; p != NULL; p = p->n_flink) {
1467 if (ntype && (p->n_type & GMASK) != ntype)
1468 continue;
1469 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1470 if ((flags & GCOMMA) && p->n_flink != NULL)
1471 *cp++ = ',';
1472 *cp++ = ' ';
1474 *--cp = 0;
1475 if ((flags & GCOMMA) && *--cp == ',')
1476 *cp = 0;
1477 jleave:
1478 NYD_LEAVE;
1479 return topp;
1482 FL struct name *
1483 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1484 int comma, enum gfield gflags)
1486 struct name *nq;
1487 NYD_ENTER;
1489 jloop:
1490 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1491 for (nq = np; nq != NULL; nq = nq->n_flink)
1492 if (is_addr_invalid(nq, EACM_NONE))
1493 goto jloop;
1494 NYD_LEAVE;
1495 return np;
1498 FL bool_t
1499 name_is_same_domain(struct name const *n1, struct name const *n2)
1501 char const *d1, *d2;
1502 bool_t rv;
1503 NYD_ENTER;
1505 d1 = strrchr(n1->n_name, '@');
1506 d2 = strrchr(n2->n_name, '@');
1508 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1510 NYD_LEAVE;
1511 return rv;
1514 FL struct name *
1515 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1516 si8_t *set_on_error)
1518 struct name *n;
1519 NYD_ENTER;
1521 for (n = np; n != NULL; n = n->n_flink) {
1522 si8_t rv;
1524 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1525 if (set_on_error != NULL)
1526 *set_on_error |= rv; /* don't loose -1! */
1527 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1528 continue;
1529 if (n->n_blink)
1530 n->n_blink->n_flink = n->n_flink;
1531 if (n->n_flink)
1532 n->n_flink->n_blink = n->n_blink;
1533 if (n == np)
1534 np = n->n_flink;
1537 NYD_LEAVE;
1538 return np;
1541 FL struct name *
1542 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1543 bool_t metoo, si8_t *set_on_error)
1545 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1546 struct name *tolist, *np, **npp;
1547 NYD_ENTER;
1549 tolist = cat(hp->h_to, cat(hp->h_cc, cat(hp->h_bcc, hp->h_fcc)));
1550 hp->h_to = hp->h_cc = hp->h_bcc = hp->h_fcc = NULL;
1552 tolist = usermap(tolist, metoo);
1553 tolist = n_alternates_remove(tolist, TRU1);
1554 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1556 for (np = tolist; np != NULL; np = np->n_flink) {
1557 switch (np->n_type & (GDEL | GMASK)) {
1558 case GTO: npp = &hp->h_to; break;
1559 case GCC: npp = &hp->h_cc; break;
1560 case GBCC: npp = &hp->h_bcc; break;
1561 default: continue;
1563 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1565 NYD_LEAVE;
1566 return tolist;
1569 FL struct name *
1570 usermap(struct name *names, bool_t force_metoo){
1571 struct a_nag_group *ngp;
1572 struct name *nlist, *nlist_tail, *np, *cp;
1573 int metoo;
1574 NYD_ENTER;
1576 metoo = (force_metoo || ok_blook(metoo));
1577 nlist = nlist_tail = NULL;
1578 np = names;
1580 for(; np != NULL; np = cp){
1581 assert(!(np->n_type & GDEL)); /* TODO legacy */
1582 cp = np->n_flink;
1584 if(is_fileorpipe_addr(np) ||
1585 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1586 if((np->n_blink = nlist_tail) != NULL)
1587 nlist_tail->n_flink = np;
1588 else
1589 nlist = np;
1590 nlist_tail = np;
1591 np->n_flink = NULL;
1592 }else{
1593 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1594 if((nlist_tail = nlist) != NULL)
1595 while(nlist_tail->n_flink != NULL)
1596 nlist_tail = nlist_tail->n_flink;
1599 NYD_LEAVE;
1600 return nlist;
1603 FL struct name *
1604 elide(struct name *names)
1606 size_t i, j, k;
1607 struct name *nlist, *np, **nparr;
1608 NYD_ENTER;
1610 nlist = NULL;
1612 if(names == NULL)
1613 goto jleave;
1615 /* Throw away all deleted nodes */
1616 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1617 if(!(names->n_type & GDEL)){
1618 names->n_blink = np;
1619 if(np != NULL)
1620 np->n_flink = names;
1621 else
1622 nlist = names;
1623 np = names;
1624 ++i;
1626 if(nlist == NULL || i == 1)
1627 goto jleave;
1628 np->n_flink = NULL;
1630 /* Create a temporay array and sort that */
1631 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1633 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1634 nparr[i++] = np;
1636 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1638 /* Remove duplicates XXX speedup, or list_uniq()! */
1639 for(j = 0, --i; j < i;){
1640 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1641 ++j;
1642 else{
1643 for(; k < i; ++k)
1644 nparr[k] = nparr[k + 1];
1645 --i;
1649 /* Throw away all list members which are not part of the array.
1650 * Note this keeps the original, possibly carefully crafted, order of the
1651 * addressees, thus */
1652 for(np = nlist; np != NULL; np = np->n_flink){
1653 for(j = 0; j <= i; ++j)
1654 if(np == nparr[j]){
1655 nparr[j] = NULL;
1656 goto jiter;
1658 /* Drop it */
1659 if(np == nlist){
1660 nlist = np->n_flink;
1661 np->n_blink = NULL;
1662 }else
1663 np->n_blink->n_flink = np->n_flink;
1664 if(np->n_flink != NULL)
1665 np->n_flink->n_blink = np->n_blink;
1666 jiter:;
1669 n_lofi_free(nparr);
1670 jleave:
1671 NYD_LEAVE;
1672 return nlist;
1675 FL int
1676 c_alternates(void *vp){
1677 struct a_nag_group *ngp;
1678 char const *varname, *ccp;
1679 char **argv;
1680 NYD_ENTER;
1682 n_pstate_err_no = n_ERR_NONE;
1684 argv = vp;
1685 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1687 if(*argv == NULL){
1688 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1689 vp = NULL;
1690 }else{
1691 if(varname != NULL)
1692 n_err(_("`alternates': `vput' only supported for show mode\n"));
1694 /* Delete the old set to "declare a list", if *posix* */
1695 if(ok_blook(posix))
1696 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1698 while((ccp = *argv++) != NULL){
1699 size_t l;
1700 struct name *np;
1702 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1703 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1704 n_err(_("Invalid `alternates' argument: %s\n"),
1705 n_shexp_quote_cp(ccp, FAL0));
1706 n_pstate_err_no = n_ERR_INVAL;
1707 vp = NULL;
1708 continue;
1710 ccp = np->n_name;
1712 l = strlen(ccp) +1;
1713 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1714 n_err(_("Failed to create storage for alternates: %s\n"),
1715 n_shexp_quote_cp(ccp, FAL0));
1716 n_pstate_err_no = n_ERR_NOMEM;
1717 vp = NULL;
1721 NYD_LEAVE;
1722 return (vp != NULL ? 0 : 1);
1725 FL int
1726 c_unalternates(void *vp){
1727 char **argv;
1728 int rv;
1729 NYD_ENTER;
1731 rv = 0;
1732 argv = vp;
1734 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1735 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1736 rv = 1;
1737 }while(*++argv != NULL);
1738 NYD_LEAVE;
1739 return rv;
1742 FL struct name *
1743 n_alternates_remove(struct name *np, bool_t keep_single){
1744 /* XXX keep a single pointer, initial null, and immediate remove nodes
1745 * XXX on successful match unless keep single and that pointer null! */
1746 struct a_nag_group_lookup ngl;
1747 struct a_nag_group *ngp;
1748 struct name *xp, *newnp;
1749 NYD_ENTER;
1751 /* Delete the temporary bit from all */
1752 for(xp = np; xp != NULL; xp = xp->n_flink)
1753 xp->n_flags &= ~(ui32_t)SI32_MIN;
1755 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1756 * and hash-lookup alternate instead (unless *allnet*) */
1757 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1758 ngp = a_nag_group_go_next(&ngl))
1759 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1761 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1763 if((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL){
1764 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1765 * TODO At assignment time, as VIP var? */
1767 np = a_nag_namelist_mark_name(np, xp->n_name);
1768 while((xp = xp->n_flink) != NULL);
1769 }else for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1770 xp = xp->n_flink)
1771 np = a_nag_namelist_mark_name(np, xp->n_name);
1773 /* C99 */{
1774 char const *v15compat;
1776 if((v15compat = ok_vlook(replyto)) != NULL){
1777 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1778 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1779 xp = xp->n_flink)
1780 np = a_nag_namelist_mark_name(np, xp->n_name);
1784 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1785 xp = xp->n_flink)
1786 np = a_nag_namelist_mark_name(np, xp->n_name);
1788 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1789 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1790 if(np->n_type & GDEL)
1791 continue;
1792 if(np->n_flags & (ui32_t)SI32_MIN){
1793 if(!keep_single)
1794 continue;
1795 keep_single = FAL0;
1798 np->n_blink = xp;
1799 if(xp != NULL)
1800 xp->n_flink = np;
1801 else
1802 newnp = np;
1803 xp = np;
1804 xp->n_flags &= ~(ui32_t)SI32_MIN;
1806 if(xp != NULL)
1807 xp->n_flink = NULL;
1808 np = newnp;
1810 NYD_LEAVE;
1811 return np;
1814 FL bool_t
1815 n_is_myname(char const *name){
1816 struct a_nag_group_lookup ngl;
1817 struct a_nag_group *ngp;
1818 struct name *xp;
1819 NYD_ENTER;
1821 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1822 goto jleave;
1824 if(!ok_blook(allnet)){
1825 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1826 goto jleave;
1827 }else{
1828 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1829 ngp = a_nag_group_go_next(&ngl))
1830 if(a_nag_is_same_name(ngp->ng_id, name))
1831 goto jleave;
1834 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1835 xp = xp->n_flink)
1836 if(a_nag_is_same_name(xp->n_name, name))
1837 goto jleave;
1839 /* C99 */{
1840 char const *v15compat;
1842 if((v15compat = ok_vlook(replyto)) != NULL){
1843 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1844 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1845 xp = xp->n_flink)
1846 if(a_nag_is_same_name(xp->n_name, name))
1847 goto jleave;
1851 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1852 xp = xp->n_flink)
1853 if(a_nag_is_same_name(xp->n_name, name))
1854 goto jleave;
1856 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1857 xp = xp->n_flink)
1858 if(a_nag_is_same_name(xp->n_name, name))
1859 goto jleave;
1861 name = NULL;
1862 jleave:
1863 NYD_LEAVE;
1864 return (name != NULL);
1867 FL int
1868 c_addrcodec(void *vp){
1869 struct n_addrguts ag;
1870 struct str trims;
1871 struct n_string s_b, *sp;
1872 size_t alen;
1873 int mode;
1874 char const **argv, *varname, *act, *cp;
1875 NYD_ENTER;
1877 sp = n_string_creat_auto(&s_b);
1878 argv = vp;
1879 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1881 act = *argv;
1882 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1884 mode = 0;
1885 if(*act == '+')
1886 mode = 1, ++act;
1887 if(*act == '+')
1888 mode = 2, ++act;
1889 if(*act == '+')
1890 mode = 3, ++act;
1891 if(act >= cp)
1892 goto jesynopsis;
1893 alen = PTR2SIZE(cp - act);
1894 if(*cp != '\0')
1895 ++cp;
1897 trims.l = strlen(trims.s = n_UNCONST(cp));
1898 cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
1899 if(trims.l <= UIZ_MAX / 4)
1900 trims.l <<= 1;
1901 sp = n_string_reserve(sp, trims.l);
1903 n_pstate_err_no = n_ERR_NONE;
1905 if(is_ascncaseprefix(act, "encode", alen)){
1906 /* This function cannot be a simple nalloc() wrapper even later on, since
1907 * we may need to turn any ", () or \ into quoted-pairs */
1908 char c;
1910 while((c = *cp++) != '\0'){
1911 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1912 (c == '\\' && mode < 3))
1913 sp = n_string_push_c(sp, '\\');
1914 sp = n_string_push_c(sp, c);
1917 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1918 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1919 ) != NAME_ADDRSPEC_ISADDR){
1920 cp = sp->s_dat;
1921 n_pstate_err_no = n_ERR_INVAL;
1922 vp = NULL;
1923 }else{
1924 struct name *np;
1926 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1927 cp = np->n_fullname;
1929 }else if(mode == 0){
1930 if(is_ascncaseprefix(act, "decode", alen)){
1931 char c;
1933 while((c = *cp++) != '\0'){
1934 switch(c){
1935 case '(':
1936 sp = n_string_push_c(sp, '(');
1937 act = skip_comment(cp);
1938 if(--act > cp)
1939 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1940 sp = n_string_push_c(sp, ')');
1941 cp = ++act;
1942 break;
1943 case '"':
1944 while(*cp != '\0'){
1945 if((c = *cp++) == '"')
1946 break;
1947 if(c == '\\' && (c = *cp) != '\0')
1948 ++cp;
1949 sp = n_string_push_c(sp, c);
1951 break;
1952 default:
1953 if(c == '\\' && (c = *cp++) == '\0')
1954 break;
1955 sp = n_string_push_c(sp, c);
1956 break;
1959 cp = n_string_cp(sp);
1960 }else if(is_ascncaseprefix(act, "skin", alen) ||
1961 (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
1962 /* Let's just use the is-single-address hack for this one, too.. */
1963 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1964 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1965 ) != NAME_ADDRSPEC_ISADDR){
1966 n_pstate_err_no = n_ERR_INVAL;
1967 vp = NULL;
1968 }else{
1969 struct name *np;
1971 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1972 cp = np->n_name;
1974 if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
1975 n_pstate_err_no = n_ERR_EXIST;
1977 }else
1978 goto jesynopsis;
1979 }else
1980 goto jesynopsis;
1982 if(varname == NULL){
1983 if(fprintf(n_stdout, "%s\n", cp) < 0){
1984 n_pstate_err_no = n_err_no;
1985 vp = NULL;
1987 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1988 n_pstate_err_no = n_ERR_NOTSUP;
1989 vp = NULL;
1992 jleave:
1993 NYD_LEAVE;
1994 return (vp != NULL ? 0 : 1);
1995 jesynopsis:
1996 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1997 "<rest-of-line>\n"));
1998 n_pstate_err_no = n_ERR_INVAL;
1999 vp = NULL;
2000 goto jleave;
2003 FL int
2004 c_commandalias(void *vp){
2005 struct a_nag_group *ngp;
2006 char const **argv, *ccp;
2007 int rv;
2008 NYD_ENTER;
2010 rv = 0;
2011 argv = vp;
2013 if((ccp = *argv) == NULL){
2014 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
2015 goto jleave;
2018 /* Verify the name is a valid one, and not a command modifier.
2019 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2020 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
2021 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
2022 !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
2023 !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
2024 n_err(_("`commandalias': not a valid command name: %s\n"),
2025 n_shexp_quote_cp(ccp, FAL0));
2026 rv = 1;
2027 goto jleave;
2030 if(argv[1] == NULL){
2031 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
2032 a_nag_group_print(ngp, n_stdout, NULL);
2033 else{
2034 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
2035 rv = 1;
2037 }else{
2038 /* Because one hardly ever redefines, anything is stored in one chunk */
2039 char *cp;
2040 size_t i, len;
2042 /* Delete the old one, if any; don't get fooled to remove them all */
2043 if(ccp[0] != '*' || ccp[1] != '\0')
2044 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2046 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2047 len += strlen(argv[i]) + 1;
2048 if(len == 0)
2049 len = 1;
2051 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2052 n_err(_("Failed to create storage for commandalias: %s\n"),
2053 n_shexp_quote_cp(ccp, FAL0));
2054 rv = 1;
2055 }else{
2056 struct a_nag_cmd_alias *ncap;
2058 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2059 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2060 cp += sizeof *ncap;
2061 ncap->nca_expand.s = cp;
2062 ncap->nca_expand.l = len - 1;
2064 for(len = 0; (ccp = *argv++) != NULL;)
2065 if((i = strlen(ccp)) > 0){
2066 if(len++ != 0)
2067 *cp++ = ' ';
2068 memcpy(cp, ccp, i);
2069 cp += i;
2071 *cp = '\0';
2074 jleave:
2075 NYD_LEAVE;
2076 return rv;
2079 FL int
2080 c_uncommandalias(void *vp){
2081 char **argv;
2082 int rv;
2083 NYD_ENTER;
2085 rv = 0;
2086 argv = vp;
2088 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2089 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2090 rv = 1;
2091 }while(*++argv != NULL);
2092 NYD_LEAVE;
2093 return rv;
2096 FL char const *
2097 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2098 struct a_nag_group *ngp;
2099 NYD_ENTER;
2101 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2102 name = ngp->ng_id;
2104 if(expansion_or_null != NULL){
2105 struct a_nag_cmd_alias *ncap;
2107 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2108 *expansion_or_null = &ncap->nca_expand;
2110 }else
2111 name = NULL;
2112 NYD_LEAVE;
2113 return name;
2116 FL bool_t
2117 n_alias_is_valid_name(char const *name){
2118 char c;
2119 char const *cp;
2120 bool_t rv;
2121 NYD2_ENTER;
2123 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2124 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2125 * i.e., [[:alnum:]_#:@.-]+$?.
2126 * As extensions allow high-bit bytes, semicolon and period. */
2127 if(!alnumchar(c) && c != '_' && c != '-' &&
2128 c != '#' && c != ':' && c != '@' &&
2129 !((ui8_t)c & 0x80) && c != '!' && c != '.'){
2130 if(c == '$' && cp != name && *cp == '\0')
2131 break;
2132 rv = FAL0;
2133 break;
2135 NYD2_LEAVE;
2136 return rv;
2139 FL int
2140 c_alias(void *v)
2142 char const *ecp;
2143 char **argv;
2144 struct a_nag_group *ngp;
2145 int rv;
2146 NYD_ENTER;
2148 rv = 0;
2149 argv = v;
2150 n_UNINIT(ecp, NULL);
2152 if(*argv == NULL)
2153 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2154 else if(!n_alias_is_valid_name(*argv)){
2155 ecp = N_("Not a valid alias name: %s\n");
2156 goto jerr;
2157 }else if(argv[1] == NULL){
2158 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2159 a_nag_group_print(ngp, n_stdout, NULL);
2160 else{
2161 ecp = N_("No such alias: %s\n");
2162 goto jerr;
2164 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2165 ecp = N_("Failed to create alias storage for: %s\n");
2166 jerr:
2167 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2168 rv = 1;
2169 }else{
2170 struct a_nag_grp_names *ngnp_tail, *ngnp;
2171 struct a_nag_grp_names_head *ngnhp;
2173 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2175 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2176 while((ngnp = ngnp_tail->ngn_next) != NULL)
2177 ngnp_tail = ngnp;
2179 for(++argv; *argv != NULL; ++argv){
2180 size_t i;
2182 i = strlen(*argv) +1;
2183 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2184 if(ngnp_tail != NULL)
2185 ngnp_tail->ngn_next = ngnp;
2186 else
2187 ngnhp->ngnh_head = ngnp;
2188 ngnp_tail = ngnp;
2189 ngnp->ngn_next = NULL;
2190 memcpy(ngnp->ngn_id, *argv, i);
2193 NYD_LEAVE;
2194 return rv;
2197 FL int
2198 c_unalias(void *v){
2199 char **argv;
2200 int rv;
2201 NYD_ENTER;
2203 rv = 0;
2204 argv = v;
2206 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2207 n_err(_("No such alias: %s\n"), *argv);
2208 rv = 1;
2209 }while(*++argv != NULL);
2210 NYD_LEAVE;
2211 return rv;
2214 FL int
2215 c_mlist(void *v){
2216 int rv;
2217 NYD_ENTER;
2219 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2220 NYD_LEAVE;
2221 return rv;
2224 FL int
2225 c_unmlist(void *v){
2226 int rv;
2227 NYD_ENTER;
2229 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2230 NYD_LEAVE;
2231 return rv;
2234 FL int
2235 c_mlsubscribe(void *v){
2236 int rv;
2237 NYD_ENTER;
2239 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2240 NYD_LEAVE;
2241 return rv;
2244 FL int
2245 c_unmlsubscribe(void *v){
2246 int rv;
2247 NYD_ENTER;
2249 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2250 NYD_LEAVE;
2251 return rv;
2254 FL enum mlist_state
2255 is_mlist(char const *name, bool_t subscribed_only){
2256 struct a_nag_group *ngp;
2257 #ifdef HAVE_REGEX
2258 struct a_nag_grp_regex **lpp, *ngrp;
2259 bool_t re2;
2260 #endif
2261 enum mlist_state rv;
2262 NYD_ENTER;
2264 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2265 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2267 if(rv == MLIST_KNOWN){
2268 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2269 rv = MLIST_SUBSCRIBED;
2270 else if(subscribed_only)
2271 rv = MLIST_OTHER;
2272 /* Of course, if that is a regular expression it doesn't mean a thing */
2273 #ifdef HAVE_REGEX
2274 if(ngp->ng_type & a_NAG_T_REGEX)
2275 rv = MLIST_OTHER;
2276 else
2277 #endif
2278 goto jleave;
2281 /* Not in the hashmap (as something matchable), walk the lists */
2282 #ifdef HAVE_REGEX
2283 re2 = FAL0;
2284 lpp = &a_nag_mlsub_regex;
2286 jregex_redo:
2287 if((ngrp = *lpp) != NULL){
2288 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2289 /* Relink as the head of this list if the hit count of this group is
2290 * >= 25% of the average hit count */
2291 size_t i;
2293 if(!re2)
2294 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2295 else
2296 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2297 i >>= 2;
2299 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2300 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2301 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2302 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2303 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2304 *lpp = ngrp;
2306 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2307 goto jleave;
2308 }while((ngrp = ngrp->ngr_next) != *lpp);
2311 if(!re2 && !subscribed_only){
2312 re2 = TRU1;
2313 lpp = &a_nag_mlist_regex;
2314 goto jregex_redo;
2316 assert(rv == MLIST_OTHER);
2317 #endif /* HAVE_REGEX */
2319 jleave:
2320 NYD_LEAVE;
2321 return rv;
2324 FL enum mlist_state
2325 is_mlist_mp(struct message *mp, enum mlist_state what){
2326 struct name *np;
2327 bool_t cc;
2328 enum mlist_state rv;
2329 NYD_ENTER;
2331 rv = MLIST_OTHER;
2333 cc = FAL0;
2334 np = lextract(hfield1("to", mp), GTO | GSKIN);
2335 jredo:
2336 for(; np != NULL; np = np->n_flink){
2337 switch(is_mlist(np->n_name, FAL0)){
2338 case MLIST_OTHER:
2339 break;
2340 case MLIST_KNOWN:
2341 if(what == MLIST_KNOWN || what == MLIST_OTHER){
2342 if(rv == MLIST_OTHER)
2343 rv = MLIST_KNOWN;
2344 if(what == MLIST_KNOWN)
2345 goto jleave;
2347 break;
2348 case MLIST_SUBSCRIBED:
2349 if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
2350 if(rv != MLIST_SUBSCRIBED)
2351 rv = MLIST_SUBSCRIBED;
2352 goto jleave;
2354 break;
2358 if(!cc){
2359 cc = TRU1;
2360 np = lextract(hfield1("cc", mp), GCC | GSKIN);
2361 goto jredo;
2363 jleave:
2364 NYD_LEAVE;
2365 return rv;
2368 FL int
2369 c_shortcut(void *vp){
2370 struct a_nag_group *ngp;
2371 char **argv;
2372 int rv;
2373 NYD_ENTER;
2375 rv = 0;
2376 argv = vp;
2378 if(*argv == NULL)
2379 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2380 else if(argv[1] == NULL){
2381 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2382 a_nag_group_print(ngp, n_stdout, NULL);
2383 else{
2384 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2385 rv = 1;
2387 }else for(; *argv != NULL; argv += 2){
2388 /* Because one hardly ever redefines, anything is stored in one chunk */
2389 size_t l;
2390 char *cp;
2392 if(argv[1] == NULL){
2393 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2394 rv = 1;
2395 break;
2397 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2398 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2400 l = strlen(argv[1]) +1;
2401 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2402 n_err(_("Failed to create storage for shortcut: %s\n"),
2403 n_shexp_quote_cp(*argv, FAL0));
2404 rv = 1;
2405 }else{
2406 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2407 memcpy(cp, argv[1], l);
2410 NYD_LEAVE;
2411 return rv;
2414 FL int
2415 c_unshortcut(void *vp){
2416 char **argv;
2417 int rv;
2418 NYD_ENTER;
2420 rv = 0;
2421 argv = vp;
2423 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2424 n_err(_("No such shortcut: %s\n"), *argv);
2425 rv = 1;
2426 }while(*++argv != NULL);
2427 NYD_LEAVE;
2428 return rv;
2431 FL char const *
2432 shortcut_expand(char const *str){
2433 struct a_nag_group *ngp;
2434 NYD_ENTER;
2436 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2437 a_NAG_GP_TO_SUBCLASS(str, ngp);
2438 else
2439 str = NULL;
2440 NYD_LEAVE;
2441 return str;
2444 FL int
2445 c_charsetalias(void *vp){
2446 struct a_nag_group *ngp;
2447 char **argv;
2448 int rv;
2449 NYD_ENTER;
2451 rv = 0;
2452 argv = vp;
2454 if(*argv == NULL)
2455 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2456 else if(argv[1] == NULL){
2457 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2458 a_nag_group_print(ngp, n_stdout, NULL);
2459 else{
2460 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2461 rv = 1;
2463 }else for(; *argv != NULL; argv += 2){
2464 /* Because one hardly ever redefines, anything is stored in one chunk */
2465 char *cp;
2466 size_t dstl;
2467 char const *dst, *src;
2469 if((dst = argv[1]) == NULL){
2470 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2471 rv = 1;
2472 break;
2473 }else if((dst = n_iconv_normalize_name(dst)) == NULL){
2474 n_err(_("charsetalias: invalid target charset %s\n"),
2475 n_shexp_quote_cp(argv[1], FAL0));
2476 rv = 1;
2477 continue;
2478 }else if((src = n_iconv_normalize_name(argv[0])) == NULL){
2479 n_err(_("charsetalias: invalid source charset %s\n"),
2480 n_shexp_quote_cp(argv[0], FAL0));
2481 rv = 1;
2482 continue;
2485 /* Delete the old one, if any; don't get fooled to remove them all */
2486 if(src[0] != '*' || src[1] != '\0')
2487 a_nag_group_del(a_NAG_T_CHARSETALIAS, src);
2489 dstl = strlen(dst) +1;
2490 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, src, dstl)) == NULL){
2491 n_err(_("Failed to create storage for charsetalias: %s\n"),
2492 n_shexp_quote_cp(src, FAL0));
2493 rv = 1;
2494 }else{
2495 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2496 memcpy(cp, dst, dstl);
2499 NYD_LEAVE;
2500 return rv;
2503 FL int
2504 c_uncharsetalias(void *vp){
2505 char **argv, *cp;
2506 int rv;
2507 NYD_ENTER;
2509 rv = 0;
2510 argv = vp;
2513 if((cp = n_iconv_normalize_name(*argv)) == NULL ||
2514 !a_nag_group_del(a_NAG_T_CHARSETALIAS, cp)){
2515 n_err(_("No such `charsetalias': %s\n"),
2516 n_shexp_quote_cp(*argv, FAL0));
2517 rv = 1;
2519 }while(*++argv != NULL);
2520 NYD_LEAVE;
2521 return rv;
2524 FL char const *
2525 n_charsetalias_expand(char const *cp){
2526 struct a_nag_group *ngp;
2527 size_t i;
2528 char const *cp_orig;
2529 NYD_ENTER;
2531 cp_orig = cp;
2533 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2534 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2535 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2536 break;
2539 if(cp != cp_orig)
2540 cp = savestr(cp);
2541 NYD_LEAVE;
2542 return cp;
2545 FL int
2546 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2547 struct a_nag_group *ngp;
2548 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2549 int rv;
2550 NYD_ENTER;
2552 rv = 0;
2553 argv = vp;
2555 if(*argv == NULL)
2556 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2557 else if(argv[1] == NULL){
2558 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2559 a_nag_group_print(ngp, n_stdout, NULL);
2560 else{
2561 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2562 rv = 1;
2564 }else for(; *argv != NULL; argv += 3){
2565 /* Because one hardly ever redefines, anything is stored in one chunk */
2566 char const *ccp;
2567 char *cp, c;
2568 size_t llc, lsc;
2570 if(argv[1] == NULL || argv[2] == NULL){
2571 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2572 rv = 1;
2573 break;
2576 /* Delete the old one, if any; don't get fooled to remove them all */
2577 ccp = argv[0];
2578 if(ccp[0] != '*' || ccp[1] != '\0')
2579 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2581 /* Lowercase it all (for display purposes) */
2582 cp = savestr(ccp);
2583 ccp = cp;
2584 while((c = *cp) != '\0')
2585 *cp++ = lowerconv(c);
2587 llc = strlen(argv[1]) +1;
2588 lsc = strlen(argv[2]) +1;
2589 if(UIZ_MAX - llc <= lsc)
2590 goto jenomem;
2592 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2593 jenomem:
2594 n_err(_("Failed to create storage for filetype: %s\n"),
2595 n_shexp_quote_cp(argv[0], FAL0));
2596 rv = 1;
2597 }else{
2598 struct a_nag_file_type *nftp;
2600 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2601 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2602 cp += sizeof *nftp;
2603 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2604 cp += llc;
2605 nftp->nft_load.l = --llc;
2606 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2607 /*cp += lsc;*/
2608 nftp->nft_save.l = --lsc;
2611 NYD_LEAVE;
2612 return rv;
2615 FL int
2616 c_unfiletype(void *vp){
2617 char **argv;
2618 int rv;
2619 NYD_ENTER;
2621 rv = 0;
2622 argv = vp;
2624 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2625 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2626 rv = 1;
2627 }while(*++argv != NULL);
2628 NYD_LEAVE;
2629 return rv;
2632 FL bool_t
2633 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2634 struct stat stb;
2635 struct a_nag_group_lookup ngl;
2636 struct n_string s, *sp;
2637 struct a_nag_group const *ngp;
2638 ui32_t l;
2639 NYD2_ENTER;
2641 sp = n_string_creat_auto(&s);
2642 sp = n_string_assign_cp(sp, file);
2643 sp = n_string_push_c(sp, '.');
2644 l = sp->s_len;
2646 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2647 ngp = a_nag_group_go_next(&ngl)){
2648 sp = n_string_trunc(sp, l);
2649 sp = n_string_push_buf(sp, ngp->ng_id,
2650 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2652 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2653 if(res_or_null != NULL){
2654 struct a_nag_file_type *nftp;
2656 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2657 res_or_null->ft_ext_dat = ngp->ng_id;
2658 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2659 res_or_null->ft_load_dat = nftp->nft_load.s;
2660 res_or_null->ft_load_len = nftp->nft_load.l;
2661 res_or_null->ft_save_dat = nftp->nft_save.s;
2662 res_or_null->ft_save_len = nftp->nft_save.l;
2664 goto jleave; /* TODO after v15 legacy drop: break; */
2668 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2669 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2670 ngp = (struct a_nag_group*)0x1;
2672 sp = n_string_trunc(sp, l);
2673 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2674 a_nag_OBSOLETE_xz.ft_ext_len);
2675 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2676 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2677 if(res_or_null != NULL)
2678 *res_or_null = a_nag_OBSOLETE_xz;
2679 goto jleave;
2682 sp = n_string_trunc(sp, l);
2683 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2684 a_nag_OBSOLETE_gz.ft_ext_len);
2685 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2686 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2687 if(res_or_null != NULL)
2688 *res_or_null = a_nag_OBSOLETE_gz;
2689 goto jleave;
2692 sp = n_string_trunc(sp, l);
2693 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2694 a_nag_OBSOLETE_bz2.ft_ext_len);
2695 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2696 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2697 if(res_or_null != NULL)
2698 *res_or_null = a_nag_OBSOLETE_bz2;
2699 goto jleave;
2702 ngp = NULL;
2704 jleave:
2705 NYD2_LEAVE;
2706 return (ngp != NULL);
2709 FL bool_t
2710 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2711 char const *ext, *lext;
2712 NYD2_ENTER;
2714 if((ext = strrchr(file, '/')) != NULL)
2715 file = ++ext;
2717 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2718 struct a_nag_group const *ngp;
2720 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2721 lext = ext;
2722 if(res_or_null != NULL){
2723 struct a_nag_file_type *nftp;
2725 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2726 res_or_null->ft_ext_dat = ngp->ng_id;
2727 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2728 res_or_null->ft_load_dat = nftp->nft_load.s;
2729 res_or_null->ft_load_len = nftp->nft_load.l;
2730 res_or_null->ft_save_dat = nftp->nft_save.s;
2731 res_or_null->ft_save_len = nftp->nft_save.l;
2733 goto jleave; /* TODO after v15 legacy drop: break; */
2737 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2738 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2739 if(lext == NULL)
2740 goto jleave;
2742 if(!asccasecmp(lext, "xz")){
2743 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2744 if(res_or_null != NULL)
2745 *res_or_null = a_nag_OBSOLETE_xz;
2746 goto jleave;
2747 }else if(!asccasecmp(lext, "gz")){
2748 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2749 if(res_or_null != NULL)
2750 *res_or_null = a_nag_OBSOLETE_gz;
2751 goto jleave;
2752 }else if(!asccasecmp(lext, "bz2")){
2753 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2754 if(res_or_null != NULL)
2755 *res_or_null = a_nag_OBSOLETE_bz2;
2756 goto jleave;
2757 }else{
2758 char const *cload, *csave;
2759 char *vbuf;
2760 size_t l;
2762 #undef a_X1
2763 #define a_X1 "file-hook-load-"
2764 #undef a_X2
2765 #define a_X2 "file-hook-save-"
2766 l = strlen(lext);
2767 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2769 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2770 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2771 vbuf[sizeof(a_X1) -1 + l] = '\0';
2772 cload = n_var_vlook(vbuf, FAL0);
2774 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2775 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2776 vbuf[sizeof(a_X2) -1 + l] = '\0';
2777 csave = n_var_vlook(vbuf, FAL0);
2779 #undef a_X2
2780 #undef a_X1
2781 n_lofi_free(vbuf);
2783 if((csave != NULL) | (cload != NULL)){
2784 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2785 "please use the `filetype' command");
2787 if(((csave != NULL) ^ (cload != NULL)) == 0){
2788 if(res_or_null != NULL){
2789 res_or_null->ft_ext_dat = lext;
2790 res_or_null->ft_ext_len = l;
2791 res_or_null->ft_load_dat = cload;
2792 res_or_null->ft_load_len = strlen(cload);
2793 res_or_null->ft_save_dat = csave;
2794 res_or_null->ft_save_len = strlen(csave);
2796 goto jleave;
2797 }else
2798 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2799 lext);
2803 lext = NULL;
2805 jleave:
2806 NYD2_LEAVE;
2807 return (lext != NULL);
2810 /* s-it-mode */