make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / nam-a-grp.c
blob647edb302d7feeeb51e523311d89cddabc786b48
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 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
8 */
9 /*
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
37 #undef n_FILE
38 #define n_FILE nam_a_grp
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum a_nag_type{
45 /* Main types */
46 a_NAG_T_ALTERNATES = 1,
47 a_NAG_T_COMMANDALIAS,
48 a_NAG_T_ALIAS,
49 a_NAG_T_MLIST,
50 a_NAG_T_SHORTCUT,
51 a_NAG_T_CHARSETALIAS,
52 a_NAG_T_FILETYPE,
53 a_NAG_T_MASK = 0x1F,
55 /* Subtype bits and flags */
56 a_NAG_T_SUBSCRIBE = 1u<<6,
57 a_NAG_T_REGEX = 1u<<7,
59 /* Extended type mask to be able to reflect what we really have; i.e., mlist
60 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
61 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
62 * attribute set */
63 a_NAG_T_PRINT_MASK = a_NAG_T_MASK | a_NAG_T_SUBSCRIBE
65 n_CTA(a_NAG_T_MASK >= a_NAG_T_FILETYPE, "Mask does not cover necessary bits");
67 struct a_nag_group{
68 struct a_nag_group *ng_next;
69 ui32_t ng_subclass_off; /* of "subclass" in .ng_id (if any) */
70 ui16_t ng_id_len_sub; /* length of .ng_id: _subclass_off - this */
71 ui8_t ng_type; /* enum a_nag_type */
72 /* Identifying name, of variable size. Dependent on actual "subtype" more
73 * data follows thereafter, but note this is always used (i.e., for regular
74 * expression entries this is still set to the plain string) */
75 char ng_id[n_VFIELD_SIZE(1)];
77 #define a_NAG_GP_TO_SUBCLASS(X,G) \
78 do{\
79 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
80 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
81 (X) = a__gs__.gs_vp;\
82 }while(0)
84 struct a_nag_grp_names_head{
85 struct a_nag_grp_names *ngnh_head;
88 struct a_nag_grp_names{
89 struct a_nag_grp_names *ngn_next;
90 char ngn_id[n_VFIELD_SIZE(0)];
93 #ifdef HAVE_REGEX
94 struct a_nag_grp_regex{
95 struct a_nag_grp_regex *ngr_last;
96 struct a_nag_grp_regex *ngr_next;
97 struct a_nag_group *ngr_mygroup; /* xxx because lists use grp_regex*! ?? */
98 size_t ngr_hits; /* Number of times this group matched */
99 regex_t ngr_regex;
101 #endif
103 struct a_nag_cmd_alias{
104 struct str nca_expand;
107 struct a_nag_file_type{
108 struct str nft_load;
109 struct str nft_save;
112 struct a_nag_group_lookup{
113 struct a_nag_group **ngl_htable;
114 struct a_nag_group **ngl_slot;
115 struct a_nag_group *ngl_slot_last;
116 struct a_nag_group *ngl_group;
119 static struct n_file_type const a_nag_OBSOLETE_xz = { /* TODO v15 compat */
120 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
121 }, a_nag_OBSOLETE_gz = {
122 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
123 }, a_nag_OBSOLETE_bz2 = {
124 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
125 "bzip2 -cz", sizeof("bzip2 -cz") -1
128 /* `alternates' */
129 static struct a_nag_group *a_nag_alternates_heads[HSHSIZE];
131 /* `commandalias' */
132 static struct a_nag_group *a_nag_commandalias_heads[HSHSIZE];
134 /* `alias' */
135 static struct a_nag_group *a_nag_alias_heads[HSHSIZE];
137 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
138 static struct a_nag_group *a_nag_mlist_heads[HSHSIZE];
140 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
141 * be accessed via sequential lists instead, which are type-specific for better
142 * performance, but also to make it possible to have ".*@xy.org" as a mlist
143 * and "(one|two)@xy.org" as a mlsubscription.
144 * These lists use a bit of QOS optimization in that a matching group will
145 * become relinked as the new list head if its hit count is
146 * (>= ((xy_hits / _xy_size) >> 2))
147 * Note that the hit counts only count currently linked in nodes.. */
148 #ifdef HAVE_REGEX
149 static struct a_nag_grp_regex *a_nag_mlist_regex;
150 static struct a_nag_grp_regex *a_nag_mlsub_regex;
151 static size_t a_nag_mlist_size;
152 static size_t a_nag_mlist_hits;
153 static size_t a_nag_mlsub_size;
154 static size_t a_nag_mlsub_hits;
155 #endif
157 /* `shortcut' */
158 static struct a_nag_group *a_nag_shortcut_heads[HSHSIZE];
160 /* `charsetalias' */
161 static struct a_nag_group *a_nag_charsetalias_heads[HSHSIZE];
163 /* `filetype' */
164 static struct a_nag_group *a_nag_filetype_heads[HSHSIZE];
166 /* Same name, while taking care for *allnet*? */
167 static bool_t a_nag_is_same_name(char const *n1, char const *n2);
169 /* Mark all (!file, !pipe) nodes with the given name */
170 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
172 /* Grab a single name (liberal name) */
173 static char const *a_nag_yankname(char const *ap, char *wbuf,
174 char const *separators, int keepcomms);
176 /* Extraction multiplexer that splits an input line to names */
177 static struct name *a_nag_extract1(char const *line, enum gfield ntype,
178 char const *separators, bool_t keepcomms);
180 /* Recursively expand a alias name. Limit expansion to some fixed level.
181 * Direct recursion is not expanded for convenience */
182 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
183 struct a_nag_group *ngp, bool_t metoo, int ntype);
185 /* elide() helper */
186 static int a_nag_elide_qsort(void const *s1, void const *s2);
188 /* Lookup a group, return it or NULL, fill in glp anyway */
189 static struct a_nag_group *a_nag_group_lookup(enum a_nag_type nt,
190 struct a_nag_group_lookup *nglp, char const *id);
192 /* Easier-to-use wrapper around _group_lookup() */
193 static struct a_nag_group *a_nag_group_find(enum a_nag_type nt, char const *id);
195 /* Iteration: go to the first group, which also inits the iterator. A valid
196 * iterator can be stepped via _next(). A NULL return means no (more) groups
197 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
198 static struct a_nag_group *a_nag_group_go_first(enum a_nag_type nt,
199 struct a_nag_group_lookup *nglp);
200 static struct a_nag_group *a_nag_group_go_next(struct a_nag_group_lookup *nglp);
202 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
203 static struct a_nag_group *a_nag_group_fetch(enum a_nag_type nt, char const *id,
204 size_t addsz);
206 /* "Intelligent" delete which handles a "*" id, too;
207 * returns a true boolean if a group was deleted, and always succeeds for "*" */
208 static bool_t a_nag_group_del(enum a_nag_type nt, char const *id);
210 static struct a_nag_group *a_nag__group_del(struct a_nag_group_lookup *nglp);
211 static void a_nag__names_del(struct a_nag_group *ngp);
213 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
214 * only in this mode it can return failure */
215 static bool_t a_nag_group_print_all(enum a_nag_type nt,
216 char const *varname);
218 static int a_nag__group_print_qsorter(void const *a, void const *b);
220 /* Really print a group, actually. Or store in vputsp, if set.
221 * Return number of written lines */
222 static size_t a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
223 struct n_string *vputsp);
225 /* Multiplexers for list and subscribe commands */
226 static int a_nag_mlmux(enum a_nag_type nt, char const **argv);
227 static int a_nag_unmlmux(enum a_nag_type nt, char const **argv);
229 /* Relinkers for the sequential match lists */
230 #ifdef HAVE_REGEX
231 static void a_nag_mlmux_linkin(struct a_nag_group *ngp);
232 static void a_nag_mlmux_linkout(struct a_nag_group *ngp);
233 # define a_NAG_MLMUX_LINKIN(GP) \
234 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
235 # define a_NAG_MLMUX_LINKOUT(GP) \
236 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
237 #else
238 # define a_NAG_MLMUX_LINKIN(GP)
239 # define a_NAG_MLMUX_LINKOUT(GP)
240 #endif
242 static bool_t
243 a_nag_is_same_name(char const *n1, char const *n2){
244 bool_t rv;
245 char c1, c2, c1r, c2r;
246 NYD2_ENTER;
248 if(ok_blook(allnet)){
249 for(;; ++n1, ++n2){
250 c1 = *n1;
251 c1 = lowerconv(c1);
252 c1r = (c1 == '\0' || c1 == '@');
253 c2 = *n2;
254 c2 = lowerconv(c2);
255 c2r = (c2 == '\0' || c2 == '@');
257 if(c1r || c2r){
258 rv = (c1r == c2r);
259 break;
260 }else if(c1 != c2){
261 rv = FAL0;
262 break;
265 }else
266 rv = !asccasecmp(n1, n2);
267 NYD2_LEAVE;
268 return rv;
271 static struct name *
272 a_nag_namelist_mark_name(struct name *np, char const *name){
273 struct name *p;
274 NYD2_ENTER;
276 for(p = np; p != NULL; p = p->n_flink)
277 if(!(p->n_type & GDEL) &&
278 !(p->n_flags & (((ui32_t)SI32_MIN) | NAME_ADDRSPEC_ISFILE |
279 NAME_ADDRSPEC_ISPIPE)) &&
280 a_nag_is_same_name(p->n_name, name))
281 p->n_flags |= (ui32_t)SI32_MIN;
282 NYD2_LEAVE;
283 return np;
286 static char const *
287 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
288 int keepcomms)
290 char const *cp;
291 char *wp, c, inquote, lc, lastsp;
292 NYD_ENTER;
294 *(wp = wbuf) = '\0';
296 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
297 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
299 if (c == '\0') {
300 cp = NULL;
301 goto jleave;
304 /* Parse a full name: TODO RFC 5322
305 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
306 * - Skip entire (nested) comments
307 * - In non-quote, non-comment, join adjacent space to a single SP
308 * - Understand separators only in non-quote, non-comment context,
309 * and only if not part of a *quoted-pair* (XXX too liberal) */
310 cp = ap;
311 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
312 c = *cp;
313 if (c == '\0')
314 break;
315 if (c == '\\')
316 goto jwpwc;
317 if (c == '"') {
318 if (lc != '\\')
319 inquote = !inquote;
320 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
321 else
322 --wp;
323 #endif
324 goto jwpwc;
326 if (inquote || lc == '\\') {
327 jwpwc:
328 *wp++ = c;
329 lastsp = 0;
330 continue;
332 if (c == '(') {
333 ap = cp;
334 cp = skip_comment(cp + 1);
335 if (keepcomms)
336 while (ap < cp)
337 *wp++ = *ap++;
338 --cp;
339 lastsp = 0;
340 continue;
342 if (strchr(separators, c) != NULL)
343 break;
345 lc = lastsp;
346 lastsp = blankchar(c);
347 if (!lastsp || !lc)
348 *wp++ = c;
350 if (blankchar(lc))
351 --wp;
353 *wp = '\0';
354 jleave:
355 NYD_LEAVE;
356 return cp;
359 static struct name *
360 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
361 bool_t keepcomms)
363 struct name *topp, *np, *t;
364 char const *cp;
365 char *nbuf;
366 NYD_ENTER;
368 topp = NULL;
369 if (line == NULL || *line == '\0')
370 goto jleave;
372 np = NULL;
373 cp = line;
374 nbuf = n_alloc(strlen(line) +1);
375 while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
376 t = nalloc(nbuf, ntype);
377 if (topp == NULL)
378 topp = t;
379 else
380 np->n_flink = t;
381 t->n_blink = np;
382 np = t;
384 n_free(nbuf);
385 jleave:
386 NYD_LEAVE;
387 return topp;
390 static struct name *
391 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
392 bool_t metoo, int ntype){
393 struct a_nag_grp_names *ngnp;
394 struct name *nlist_tail;
395 char const *logname;
396 struct a_nag_grp_names_head *ngnhp;
397 NYD2_ENTER;
399 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
400 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
401 goto jleave;
404 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
405 logname = ok_vlook(LOGNAME);
407 for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
408 struct a_nag_group *xngp;
409 char *cp;
411 cp = ngnp->ngn_id;
413 if(!strcmp(cp, ngp->ng_id))
414 goto jas_is;
416 if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
417 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
418 * to a full address specification; aliases cannot be empty */
419 struct a_nag_grp_names_head *xngnhp;
421 a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
423 assert(xngnhp->ngnh_head != NULL);
424 if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
425 !a_nag_is_same_name(cp, logname))
426 nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
427 continue;
430 /* Here we should allow to expand to itself if only person in alias */
431 jas_is:
432 if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
433 !a_nag_is_same_name(cp, logname)){
434 struct name *np;
436 np = nalloc(cp, ntype | GFULL);
437 if((nlist_tail = nlist) != NULL){
438 while(nlist_tail->n_flink != NULL)
439 nlist_tail = nlist_tail->n_flink;
440 nlist_tail->n_flink = np;
441 np->n_blink = nlist_tail;
442 }else
443 nlist = np;
446 jleave:
447 NYD2_LEAVE;
448 return nlist;
451 static int
452 a_nag_elide_qsort(void const *s1, void const *s2){
453 struct name const * const *np1, * const *np2;
454 int rv;
455 NYD2_ENTER;
457 np1 = s1;
458 np2 = s2;
459 if(!(rv = asccasecmp((*np1)->n_name, (*np2)->n_name))){
460 n_LCTAV(GTO < GCC && GCC < GBCC);
461 rv = ((*np1)->n_type & (GTO | GCC | GBCC)) -
462 ((*np2)->n_type & (GTO | GCC | GBCC));
464 NYD2_LEAVE;
465 return rv;
468 static struct a_nag_group *
469 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
470 char const *id){
471 char c1;
472 struct a_nag_group *lngp, *ngp;
473 bool_t icase;
474 NYD2_ENTER;
476 icase = FAL0;
478 /* C99 */{
479 ui32_t h;
480 struct a_nag_group **ngpa;
482 switch((nt &= a_NAG_T_MASK)){
483 case a_NAG_T_ALTERNATES:
484 ngpa = a_nag_alternates_heads;
485 icase = TRU1;
486 break;
487 default:
488 case a_NAG_T_COMMANDALIAS:
489 ngpa = a_nag_commandalias_heads;
490 break;
491 case a_NAG_T_ALIAS:
492 ngpa = a_nag_alias_heads;
493 break;
494 case a_NAG_T_MLIST:
495 ngpa = a_nag_mlist_heads;
496 icase = TRU1;
497 break;
498 case a_NAG_T_SHORTCUT:
499 ngpa = a_nag_shortcut_heads;
500 break;
501 case a_NAG_T_CHARSETALIAS:
502 ngpa = a_nag_charsetalias_heads;
503 icase = TRU1;
504 break;
505 case a_NAG_T_FILETYPE:
506 ngpa = a_nag_filetype_heads;
507 icase = TRU1;
508 break;
511 nglp->ngl_htable = ngpa;
512 h = icase ? n_torek_ihash(id) : n_torek_hash(id);
513 ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
516 lngp = NULL;
517 c1 = *id++;
519 if(icase){
520 c1 = lowerconv(c1);
521 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
522 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
523 !asccasecmp(&ngp->ng_id[1], id))
524 break;
525 }else{
526 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
527 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
528 !strcmp(&ngp->ng_id[1], id))
529 break;
532 nglp->ngl_slot_last = lngp;
533 nglp->ngl_group = ngp;
534 NYD2_LEAVE;
535 return ngp;
538 static struct a_nag_group *
539 a_nag_group_find(enum a_nag_type nt, char const *id){
540 struct a_nag_group_lookup ngl;
541 struct a_nag_group *ngp;
542 NYD2_ENTER;
544 ngp = a_nag_group_lookup(nt, &ngl, id);
545 NYD2_LEAVE;
546 return ngp;
549 static struct a_nag_group *
550 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
551 size_t i;
552 struct a_nag_group **ngpa, *ngp;
553 NYD2_ENTER;
555 switch((nt &= a_NAG_T_MASK)){
556 case a_NAG_T_ALTERNATES:
557 ngpa = a_nag_alternates_heads;
558 break;
559 default:
560 case a_NAG_T_COMMANDALIAS:
561 ngpa = a_nag_commandalias_heads;
562 break;
563 case a_NAG_T_ALIAS:
564 ngpa = a_nag_alias_heads;
565 break;
566 case a_NAG_T_MLIST:
567 ngpa = a_nag_mlist_heads;
568 break;
569 case a_NAG_T_SHORTCUT:
570 ngpa = a_nag_shortcut_heads;
571 break;
572 case a_NAG_T_CHARSETALIAS:
573 ngpa = a_nag_charsetalias_heads;
574 break;
575 case a_NAG_T_FILETYPE:
576 ngpa = a_nag_filetype_heads;
577 break;
580 nglp->ngl_htable = ngpa;
582 for(i = 0; i < HSHSIZE; ++ngpa, ++i)
583 if((ngp = *ngpa) != NULL){
584 nglp->ngl_slot = ngpa;
585 nglp->ngl_group = ngp;
586 goto jleave;
589 nglp->ngl_group = ngp = NULL;
590 jleave:
591 nglp->ngl_slot_last = NULL;
592 NYD2_LEAVE;
593 return ngp;
596 static struct a_nag_group *
597 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
598 struct a_nag_group *ngp, **ngpa;
599 NYD2_ENTER;
601 if((ngp = nglp->ngl_group->ng_next) != NULL)
602 nglp->ngl_slot_last = nglp->ngl_group;
603 else{
604 nglp->ngl_slot_last = NULL;
605 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
606 if((ngp = *nglp->ngl_slot) != NULL)
607 break;
609 nglp->ngl_group = ngp;
610 NYD2_LEAVE;
611 return ngp;
614 static struct a_nag_group *
615 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
616 struct a_nag_group_lookup ngl;
617 struct a_nag_group *ngp;
618 size_t l, i;
619 NYD2_ENTER;
621 if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
622 goto jleave;
624 l = strlen(id) +1;
625 if(UIZ_MAX - n_ALIGN(l) <=
626 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
627 goto jleave;
629 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
630 switch(nt & a_NAG_T_MASK){
631 case a_NAG_T_ALTERNATES:
632 case a_NAG_T_SHORTCUT:
633 case a_NAG_T_CHARSETALIAS:
634 default:
635 break;
636 case a_NAG_T_COMMANDALIAS:
637 addsz += sizeof(struct a_nag_cmd_alias);
638 break;
639 case a_NAG_T_ALIAS:
640 addsz += sizeof(struct a_nag_grp_names_head);
641 break;
642 case a_NAG_T_MLIST:
643 #ifdef HAVE_REGEX
644 if(n_is_maybe_regex(id)){
645 addsz = sizeof(struct a_nag_grp_regex);
646 nt |= a_NAG_T_REGEX;
648 #endif
649 break;
650 case a_NAG_T_FILETYPE:
651 addsz += sizeof(struct a_nag_file_type);
652 break;
654 if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
655 goto jleave;
657 ngp = n_alloc(i + addsz);
658 memcpy(ngp->ng_id, id, l);
659 ngp->ng_subclass_off = (ui32_t)i;
660 ngp->ng_id_len_sub = (ui16_t)(i - --l);
661 ngp->ng_type = nt;
662 switch(nt & a_NAG_T_MASK){
663 case a_NAG_T_ALTERNATES:
664 case a_NAG_T_MLIST:
665 case a_NAG_T_CHARSETALIAS:
666 case a_NAG_T_FILETYPE:{
667 char *cp, c;
669 for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
670 *cp = lowerconv(c);
671 }break;
672 default:
673 break;
676 if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
677 struct a_nag_grp_names_head *ngnhp;
679 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
680 ngnhp->ngnh_head = NULL;
682 #ifdef HAVE_REGEX
683 else if(nt & a_NAG_T_REGEX){
684 int s;
685 struct a_nag_grp_regex *ngrp;
687 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
689 if((s = regcomp(&ngrp->ngr_regex, id,
690 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
691 n_err(_("Invalid regular expression: %s: %s\n"),
692 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(NULL, s));
693 n_free(ngp);
694 ngp = NULL;
695 goto jleave;
697 ngrp->ngr_mygroup = ngp;
698 a_nag_mlmux_linkin(ngp);
700 #endif /* HAVE_REGEX */
702 ngp->ng_next = *ngl.ngl_slot;
703 *ngl.ngl_slot = ngp;
704 jleave:
705 NYD2_LEAVE;
706 return ngp;
709 static bool_t
710 a_nag_group_del(enum a_nag_type nt, char const *id){
711 struct a_nag_group_lookup ngl;
712 struct a_nag_group *ngp;
713 enum a_nag_type xnt;
714 NYD2_ENTER;
716 xnt = nt & a_NAG_T_MASK;
718 /* Delete 'em all? */
719 if(id[0] == '*' && id[1] == '\0'){
720 for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
721 ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
722 : a_nag_group_go_next(&ngl);
723 ngp = (struct a_nag_group*)TRU1;
724 }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
725 if(ngp->ng_type & xnt)
726 a_nag__group_del(&ngl);
727 else
728 ngp = NULL;
730 NYD2_LEAVE;
731 return (ngp != NULL);
734 static struct a_nag_group *
735 a_nag__group_del(struct a_nag_group_lookup *nglp){
736 struct a_nag_group *x, *ngp;
737 NYD2_ENTER;
739 /* Overly complicated: link off this node, step ahead to next.. */
740 x = nglp->ngl_group;
741 if((ngp = nglp->ngl_slot_last) != NULL)
742 ngp = (ngp->ng_next = x->ng_next);
743 else{
744 nglp->ngl_slot_last = NULL;
745 ngp = (*nglp->ngl_slot = x->ng_next);
747 if(ngp == NULL){
748 struct a_nag_group **ngpa;
750 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
751 if((ngp = *nglp->ngl_slot) != NULL)
752 break;
755 nglp->ngl_group = ngp;
757 if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
758 a_nag__names_del(x);
759 #ifdef HAVE_REGEX
760 else if(x->ng_type & a_NAG_T_REGEX){
761 struct a_nag_grp_regex *ngrp;
763 a_NAG_GP_TO_SUBCLASS(ngrp, x);
765 regfree(&ngrp->ngr_regex);
766 a_nag_mlmux_linkout(x);
768 #endif
770 n_free(x);
771 NYD2_LEAVE;
772 return ngp;
775 static void
776 a_nag__names_del(struct a_nag_group *ngp){
777 struct a_nag_grp_names_head *ngnhp;
778 struct a_nag_grp_names *ngnp;
779 NYD2_ENTER;
781 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
783 for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
784 struct a_nag_grp_names *x;
786 x = ngnp;
787 ngnp = ngnp->ngn_next;
788 n_free(x);
790 NYD2_LEAVE;
793 static bool_t
794 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
795 struct n_string s;
796 size_t lines;
797 FILE *fp;
798 char const **ida;
799 struct a_nag_group const *ngp;
800 ui32_t h, i;
801 struct a_nag_group **ngpa;
802 char const *tname;
803 enum a_nag_type xnt;
804 NYD_ENTER;
806 if(varname != NULL)
807 n_string_creat_auto(&s);
809 xnt = nt & a_NAG_T_PRINT_MASK;
811 switch(xnt & a_NAG_T_MASK){
812 case a_NAG_T_ALTERNATES:
813 tname = "alternates";
814 ngpa = a_nag_alternates_heads;
815 break;
816 default:
817 case a_NAG_T_COMMANDALIAS:
818 tname = "commandalias";
819 ngpa = a_nag_commandalias_heads;
820 break;
821 case a_NAG_T_ALIAS:
822 tname = "alias";
823 ngpa = a_nag_alias_heads;
824 break;
825 case a_NAG_T_MLIST:
826 tname = "mlist";
827 ngpa = a_nag_mlist_heads;
828 break;
829 case a_NAG_T_SHORTCUT:
830 tname = "shortcut";
831 ngpa = a_nag_shortcut_heads;
832 break;
833 case a_NAG_T_CHARSETALIAS:
834 tname = "charsetalias";
835 ngpa = a_nag_charsetalias_heads;
836 break;
837 case a_NAG_T_FILETYPE:
838 tname = "filetype";
839 ngpa = a_nag_filetype_heads;
840 break;
843 /* Count entries */
844 for(i = h = 0; h < HSHSIZE; ++h)
845 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
846 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
847 ++i;
848 if(i == 0){
849 if(varname == NULL)
850 fprintf(n_stdout, _("# no %s registered\n"), tname);
851 goto jleave;
853 ++i;
854 ida = n_autorec_alloc(i * sizeof *ida);
856 /* Create alpha sorted array of entries */
857 for(i = h = 0; h < HSHSIZE; ++h)
858 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
859 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
860 ida[i++] = ngp->ng_id;
861 if(i > 1)
862 qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
863 ida[i] = NULL;
865 if(varname != NULL)
866 fp = NULL;
867 else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
868 ) == NULL)
869 fp = n_stdout;
871 /* Create visual result */
872 lines = 0;
874 switch(xnt & a_NAG_T_MASK){
875 case a_NAG_T_ALTERNATES:
876 if(fp != NULL){
877 fputs(tname, fp);
878 lines = 1;
880 break;
881 default:
882 break;
885 for(i = 0; ida[i] != NULL; ++i)
886 lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
888 #ifdef HAVE_REGEX
889 if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
890 if(nt & a_NAG_T_SUBSCRIBE)
891 i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
892 else
893 i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
895 if(i > 0 && (n_poption & n_PO_D_V)){
896 assert(fp != NULL);
897 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
898 (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
899 i, h);
900 ++lines;
903 #endif
905 switch(xnt & a_NAG_T_MASK){
906 case a_NAG_T_ALTERNATES:
907 if(fp != NULL){
908 putc('\n', fp);
909 assert(lines == 1);
911 break;
912 default:
913 break;
916 if(varname == NULL && fp != n_stdout){
917 assert(fp != NULL);
918 page_or_print(fp, lines);
919 Fclose(fp);
922 jleave:
923 if(varname != NULL){
924 tname = n_string_cp(&s);
925 if(n_var_vset(varname, (uintptr_t)tname))
926 varname = NULL;
927 else
928 n_pstate_err_no = n_ERR_NOTSUP;
930 NYD_LEAVE;
931 return (varname == NULL);
934 static int
935 a_nag__group_print_qsorter(void const *a, void const *b){
936 int rv;
937 NYD2_ENTER;
939 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
940 NYD2_LEAVE;
941 return rv;
944 static size_t
945 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
946 struct n_string *vputsp){
947 char const *cp;
948 size_t rv;
949 NYD2_ENTER;
951 rv = 1;
953 switch(ngp->ng_type & a_NAG_T_MASK){
954 case a_NAG_T_ALTERNATES:{
955 if(fo != NULL)
956 fprintf(fo, " %s", ngp->ng_id);
957 else{
958 if(vputsp->s_len > 0)
959 vputsp = n_string_push_c(vputsp, ' ');
960 /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
962 rv = 0;
963 }break;
964 case a_NAG_T_COMMANDALIAS:{
965 struct a_nag_cmd_alias *ncap;
967 assert(fo != NULL); /* xxx no vput yet */
968 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
969 fprintf(fo, "commandalias %s %s\n",
970 n_shexp_quote_cp(ngp->ng_id, TRU1),
971 n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
972 }break;
973 case a_NAG_T_ALIAS:{
974 struct a_nag_grp_names_head *ngnhp;
975 struct a_nag_grp_names *ngnp;
977 assert(fo != NULL); /* xxx no vput yet */
978 fprintf(fo, "alias %s ", ngp->ng_id);
980 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
981 if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
983 struct a_nag_grp_names *x;
985 x = ngnp;
986 ngnp = ngnp->ngn_next;
987 fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
988 }while(ngnp != NULL);
990 putc('\n', fo);
991 }break;
992 case a_NAG_T_MLIST:
993 assert(fo != NULL); /* xxx no vput yet */
994 #ifdef HAVE_REGEX
995 if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
996 size_t i;
997 struct a_nag_grp_regex *lp, *ngrp;
999 lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
1000 : a_nag_mlist_regex);
1001 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1002 for(i = 1; lp != ngrp; lp = lp->ngr_next)
1003 ++i;
1004 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
1005 ngrp->ngr_hits, i);
1006 ++rv;
1008 #endif
1009 fprintf(fo, "wysh %s %s\n",
1010 (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
1011 n_shexp_quote_cp(ngp->ng_id, TRU1));
1012 break;
1013 case a_NAG_T_SHORTCUT:
1014 assert(fo != NULL); /* xxx no vput yet */
1015 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1016 fprintf(fo, "wysh shortcut %s %s\n",
1017 ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
1018 break;
1019 case a_NAG_T_CHARSETALIAS:
1020 assert(fo != NULL); /* xxx no vput yet */
1021 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1022 fprintf(fo, "charsetalias %s %s\n",
1023 n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
1024 break;
1025 case a_NAG_T_FILETYPE:{
1026 struct a_nag_file_type *nftp;
1028 assert(fo != NULL); /* xxx no vput yet */
1029 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
1030 fprintf(fo, "filetype %s %s %s\n",
1031 n_shexp_quote_cp(ngp->ng_id, TRU1),
1032 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
1033 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
1034 }break;
1036 NYD2_LEAVE;
1037 return rv;
1040 static int
1041 a_nag_mlmux(enum a_nag_type nt, char const **argv){
1042 struct a_nag_group *ngp;
1043 char const *ecp;
1044 int rv;
1045 NYD2_ENTER;
1047 rv = 0;
1048 n_UNINIT(ecp, NULL);
1050 if(*argv == NULL)
1051 a_nag_group_print_all(nt, NULL);
1052 else do{
1053 if((ngp = a_nag_group_find(nt, *argv)) != NULL){
1054 if(nt & a_NAG_T_SUBSCRIBE){
1055 if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
1056 a_NAG_MLMUX_LINKOUT(ngp);
1057 ngp->ng_type |= a_NAG_T_SUBSCRIBE;
1058 a_NAG_MLMUX_LINKIN(ngp);
1059 }else{
1060 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
1061 goto jerr;
1063 }else{
1064 ecp = N_("Mailing-list already `mlist'ed: %s\n");
1065 goto jerr;
1067 }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
1068 ecp = N_("Failed to create storage for mailing-list: %s\n");
1069 jerr:
1070 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1071 rv = 1;
1073 }while(*++argv != NULL);
1075 NYD2_LEAVE;
1076 return rv;
1079 static int
1080 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
1081 struct a_nag_group *ngp;
1082 int rv;
1083 NYD2_ENTER;
1085 rv = 0;
1087 for(; *argv != NULL; ++argv){
1088 if(nt & a_NAG_T_SUBSCRIBE){
1089 struct a_nag_group_lookup ngl;
1090 bool_t isaster;
1092 if(!(isaster = (**argv == '*')))
1093 ngp = a_nag_group_find(nt, *argv);
1094 else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
1095 continue;
1096 else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1097 goto jaster_entry;
1099 if(ngp != NULL){
1100 jaster_redo:
1101 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1102 a_NAG_MLMUX_LINKOUT(ngp);
1103 ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
1104 a_NAG_MLMUX_LINKIN(ngp);
1106 if(isaster){
1107 jaster_entry:
1108 while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
1109 !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1111 if(ngp != NULL)
1112 goto jaster_redo;
1114 }else{
1115 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1116 n_shexp_quote_cp(*argv, FAL0));
1117 rv = 1;
1119 continue;
1121 }else if(a_nag_group_del(nt, *argv))
1122 continue;
1123 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
1124 rv = 1;
1126 NYD2_LEAVE;
1127 return rv;
1130 #ifdef HAVE_REGEX
1131 static void
1132 a_nag_mlmux_linkin(struct a_nag_group *ngp){
1133 struct a_nag_grp_regex **lpp, *ngrp, *lhp;
1134 NYD2_ENTER;
1136 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1137 lpp = &a_nag_mlsub_regex;
1138 ++a_nag_mlsub_size;
1139 }else{
1140 lpp = &a_nag_mlist_regex;
1141 ++a_nag_mlist_size;
1144 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1146 if((lhp = *lpp) != NULL){
1147 (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
1148 (ngrp->ngr_next = lhp)->ngr_last = ngrp;
1149 }else
1150 *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
1151 ngrp->ngr_hits = 0;
1152 NYD2_LEAVE;
1155 static void
1156 a_nag_mlmux_linkout(struct a_nag_group *ngp){
1157 struct a_nag_grp_regex *ngrp, **lpp;
1158 NYD2_ENTER;
1160 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1162 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1163 lpp = &a_nag_mlsub_regex;
1164 --a_nag_mlsub_size;
1165 a_nag_mlsub_hits -= ngrp->ngr_hits;
1166 }else{
1167 lpp = &a_nag_mlist_regex;
1168 --a_nag_mlist_size;
1169 a_nag_mlist_hits -= ngrp->ngr_hits;
1172 if(ngrp->ngr_next == ngrp)
1173 *lpp = NULL;
1174 else{
1175 (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
1176 if(*lpp == ngrp)
1177 *lpp = ngrp->ngr_next;
1179 NYD2_LEAVE;
1181 #endif /* HAVE_REGEX */
1183 FL struct name *
1184 nalloc(char const *str, enum gfield ntype)
1186 struct n_addrguts ag;
1187 struct str in, out;
1188 struct name *np;
1189 NYD_ENTER;
1190 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1192 str = n_addrspec_with_guts(&ag, str,
1193 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1194 if(str == NULL){
1196 np = NULL; TODO We cannot return NULL,
1197 goto jleave; TODO thus handle failures in here!
1199 str = ag.ag_input;
1202 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1203 ag.ag_n_flags |= NAME_NAME_SALLOC;
1204 np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
1205 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1206 ag.ag_skinned = (char*)(np + 1);
1207 } else
1208 np = n_autorec_alloc(sizeof *np);
1210 np->n_flink = NULL;
1211 np->n_blink = NULL;
1212 np->n_type = ntype;
1213 np->n_fullname = np->n_name = ag.ag_skinned;
1214 np->n_fullextra = NULL;
1215 np->n_flags = ag.ag_n_flags;
1217 if (ntype & GFULL) {
1218 if (ag.ag_ilen == ag.ag_slen
1219 #ifdef HAVE_IDNA
1220 && !(ag.ag_n_flags & NAME_IDNA)
1221 #endif
1223 goto jleave;
1224 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1225 goto jleave;
1227 /* n_fullextra is only the complete name part without address.
1228 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1229 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1230 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1231 char const *cp;
1233 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1234 goto jskipfullextra;
1235 i = ag.ag_ilen - e;
1236 in.s = n_lofi_alloc(s + 1 + i +1);
1237 while(s > 0 && blankchar(str[s - 1]))
1238 --s;
1239 memcpy(in.s, str, s);
1240 if (i > 0) {
1241 in.s[s++] = ' ';
1242 while (blankchar(str[e])) {
1243 ++e;
1244 if (--i == 0)
1245 break;
1247 if (i > 0)
1248 memcpy(&in.s[s], &str[e], i);
1250 s += i;
1251 in.s[in.l = s] = '\0';
1252 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1254 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1256 while (i > 0 && spacechar(cp[i - 1]))
1257 --i;
1258 np->n_fullextra = savestrbuf(cp, i);
1260 n_lofi_free(in.s);
1261 n_free(out.s);
1263 jskipfullextra:
1265 /* n_fullname depends on IDNA conversion */
1266 #ifdef HAVE_IDNA
1267 if (!(ag.ag_n_flags & NAME_IDNA)) {
1268 #endif
1269 in.s = n_UNCONST(str);
1270 in.l = ag.ag_ilen;
1271 #ifdef HAVE_IDNA
1272 } else {
1273 /* The domain name was IDNA and has been converted. We also have to
1274 * ensure that the domain name in .n_fullname is replaced with the
1275 * converted version, since MIME doesn't perform encoding of addrs */
1276 /* TODO This definetely doesn't belong here! */
1277 size_t l = ag.ag_iaddr_start,
1278 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1279 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1280 memcpy(in.s, str, l);
1281 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1282 l += ag.ag_slen;
1283 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1284 l += lsuff;
1285 in.s[l] = '\0';
1286 in.l = l;
1288 #endif
1289 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1290 np->n_fullname = savestr(out.s);
1291 n_free(out.s);
1292 #ifdef HAVE_IDNA
1293 if (ag.ag_n_flags & NAME_IDNA)
1294 n_lofi_free(in.s);
1295 #endif
1297 jleave:
1298 NYD_LEAVE;
1299 return np;
1302 FL struct name *
1303 nalloc_fcc(char const *file){
1304 struct name *nnp;
1305 NYD_ENTER;
1307 nnp = n_autorec_alloc(sizeof *nnp);
1308 nnp->n_flink = nnp->n_blink = NULL;
1309 nnp->n_type = GBCC | GBCC_IS_FCC; /* xxx Bcc: <- namelist_vaporise_head */
1310 nnp->n_flags = NAME_NAME_SALLOC | NAME_SKINNED | NAME_ADDRSPEC_ISFILE;
1311 nnp->n_fullname = nnp->n_name = savestr(file);
1312 nnp->n_fullextra = NULL;
1313 NYD_LEAVE;
1314 return nnp;
1317 FL struct name *
1318 ndup(struct name *np, enum gfield ntype)
1320 struct name *nnp;
1321 NYD_ENTER;
1323 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1324 nnp = nalloc(np->n_name, ntype);
1325 goto jleave;
1328 nnp = n_autorec_alloc(sizeof *np);
1329 nnp->n_flink = nnp->n_blink = NULL;
1330 nnp->n_type = ntype;
1331 nnp->n_flags = np->n_flags | NAME_NAME_SALLOC;
1332 nnp->n_name = savestr(np->n_name);
1333 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1334 nnp->n_fullname = nnp->n_name;
1335 nnp->n_fullextra = NULL;
1336 } else {
1337 nnp->n_fullname = savestr(np->n_fullname);
1338 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1339 : savestr(np->n_fullextra);
1341 jleave:
1342 NYD_LEAVE;
1343 return nnp;
1346 FL struct name *
1347 cat(struct name *n1, struct name *n2){
1348 struct name *tail;
1349 NYD2_ENTER;
1351 tail = n2;
1352 if(n1 == NULL)
1353 goto jleave;
1354 tail = n1;
1355 if(n2 == NULL || (n2->n_type & GDEL))
1356 goto jleave;
1358 while(tail->n_flink != NULL)
1359 tail = tail->n_flink;
1360 tail->n_flink = n2;
1361 n2->n_blink = tail;
1362 tail = n1;
1363 jleave:
1364 NYD2_LEAVE;
1365 return tail;
1368 FL struct name *
1369 n_namelist_dup(struct name const *np, enum gfield ntype){
1370 struct name *nlist, *xnp;
1371 NYD2_ENTER;
1373 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1374 struct name *x;
1376 if(!(np->n_type & GDEL)){
1377 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1378 if((x->n_blink = xnp) == NULL)
1379 nlist = x;
1380 else
1381 xnp->n_flink = x;
1382 xnp = x;
1385 NYD2_LEAVE;
1386 return nlist;
1389 FL ui32_t
1390 count(struct name const *np)
1392 ui32_t c;
1393 NYD_ENTER;
1395 for (c = 0; np != NULL; np = np->n_flink)
1396 if (!(np->n_type & GDEL))
1397 ++c;
1398 NYD_LEAVE;
1399 return c;
1402 FL ui32_t
1403 count_nonlocal(struct name const *np)
1405 ui32_t c;
1406 NYD_ENTER;
1408 for (c = 0; np != NULL; np = np->n_flink)
1409 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1410 ++c;
1411 NYD_LEAVE;
1412 return c;
1415 FL struct name *
1416 extract(char const *line, enum gfield ntype)
1418 struct name *rv;
1419 NYD_ENTER;
1421 rv = a_nag_extract1(line, ntype, " \t,", 0);
1422 NYD_LEAVE;
1423 return rv;
1426 FL struct name *
1427 lextract(char const *line, enum gfield ntype)
1429 char *cp;
1430 struct name *rv;
1431 NYD_ENTER;
1433 if(!(ntype & GSHEXP_PARSE_HACK) || !(expandaddr_to_eaf() & EAF_SHEXP_PARSE))
1434 cp = NULL;
1435 else{
1436 struct str sin;
1437 struct n_string s_b, *sp;
1438 enum n_shexp_state shs;
1440 n_autorec_relax_create();
1441 sp = n_string_creat_auto(&s_b);
1442 sin.s = n_UNCONST(line); /* logical */
1443 sin.l = UIZ_MAX;
1444 shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
1445 n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
1446 n_SHEXP_PARSE_QUOTE_AUTO_DSQ), sp, &sin, NULL);
1447 if(!(shs & n_SHEXP_STATE_ERR_MASK) && (shs & n_SHEXP_STATE_STOP)){
1448 line = cp = n_lofi_alloc(sp->s_len +1);
1449 memcpy(cp, n_string_cp(sp), sp->s_len +1);
1450 }else
1451 line = cp = NULL;
1452 n_autorec_relax_gut();
1455 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1456 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1458 if(cp != NULL)
1459 n_lofi_free(cp);
1460 NYD_LEAVE;
1461 return rv;
1464 FL char *
1465 detract(struct name *np, enum gfield ntype)
1467 char *topp, *cp;
1468 struct name *p;
1469 int flags, s;
1470 NYD_ENTER;
1472 topp = NULL;
1473 if (np == NULL)
1474 goto jleave;
1476 flags = ntype & (GCOMMA | GNAMEONLY);
1477 ntype &= ~(GCOMMA | GNAMEONLY);
1478 s = 0;
1480 for (p = np; p != NULL; p = p->n_flink) {
1481 if (ntype && (p->n_type & GMASK) != ntype)
1482 continue;
1483 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1484 if (flags & GCOMMA)
1485 ++s;
1487 if (s == 0)
1488 goto jleave;
1490 s += 2;
1491 topp = n_autorec_alloc(s);
1492 cp = topp;
1493 for (p = np; p != NULL; p = p->n_flink) {
1494 if (ntype && (p->n_type & GMASK) != ntype)
1495 continue;
1496 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1497 if ((flags & GCOMMA) && p->n_flink != NULL)
1498 *cp++ = ',';
1499 *cp++ = ' ';
1501 *--cp = 0;
1502 if ((flags & GCOMMA) && *--cp == ',')
1503 *cp = 0;
1504 jleave:
1505 NYD_LEAVE;
1506 return topp;
1509 FL struct name *
1510 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1511 int comma, enum gfield gflags)
1513 struct name *nq;
1514 NYD_ENTER;
1516 jloop:
1517 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1518 for (nq = np; nq != NULL; nq = nq->n_flink)
1519 if (is_addr_invalid(nq, EACM_NONE))
1520 goto jloop;
1521 NYD_LEAVE;
1522 return np;
1525 FL bool_t
1526 name_is_same_domain(struct name const *n1, struct name const *n2)
1528 char const *d1, *d2;
1529 bool_t rv;
1530 NYD_ENTER;
1532 d1 = strrchr(n1->n_name, '@');
1533 d2 = strrchr(n2->n_name, '@');
1535 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1537 NYD_LEAVE;
1538 return rv;
1541 FL struct name *
1542 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1543 si8_t *set_on_error)
1545 struct name *n;
1546 NYD_ENTER;
1548 for (n = np; n != NULL; n = n->n_flink) {
1549 si8_t rv;
1551 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1552 if (set_on_error != NULL)
1553 *set_on_error |= rv; /* don't loose -1! */
1554 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1555 continue;
1556 if (n->n_blink)
1557 n->n_blink->n_flink = n->n_flink;
1558 if (n->n_flink)
1559 n->n_flink->n_blink = n->n_blink;
1560 if (n == np)
1561 np = n->n_flink;
1564 NYD_LEAVE;
1565 return np;
1568 FL struct name *
1569 n_namelist_vaporise_head(bool_t strip_alternates, struct header *hp,
1570 enum expand_addr_check_mode eacm, si8_t *set_on_error)
1572 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1573 struct name *tolist, *np, **npp;
1574 NYD_ENTER;
1576 tolist = cat(hp->h_to, cat(hp->h_cc, cat(hp->h_bcc, hp->h_fcc)));
1577 hp->h_to = hp->h_cc = hp->h_bcc = hp->h_fcc = NULL;
1579 tolist = usermap(tolist, strip_alternates/*metoo*/);
1580 if(strip_alternates)
1581 tolist = n_alternates_remove(tolist, TRU1);
1582 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1584 for (np = tolist; np != NULL; np = np->n_flink) {
1585 switch (np->n_type & (GDEL | GMASK)) {
1586 case GTO: npp = &hp->h_to; break;
1587 case GCC: npp = &hp->h_cc; break;
1588 case GBCC: npp = &hp->h_bcc; break;
1589 default: continue;
1591 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1593 NYD_LEAVE;
1594 return tolist;
1597 FL struct name *
1598 usermap(struct name *names, bool_t force_metoo){
1599 struct a_nag_group *ngp;
1600 struct name *nlist, *nlist_tail, *np, *cp;
1601 int metoo;
1602 NYD_ENTER;
1604 metoo = (force_metoo || ok_blook(metoo));
1605 nlist = nlist_tail = NULL;
1606 np = names;
1608 for(; np != NULL; np = cp){
1609 assert(!(np->n_type & GDEL)); /* TODO legacy */
1610 cp = np->n_flink;
1612 if(is_fileorpipe_addr(np) ||
1613 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1614 if((np->n_blink = nlist_tail) != NULL)
1615 nlist_tail->n_flink = np;
1616 else
1617 nlist = np;
1618 nlist_tail = np;
1619 np->n_flink = NULL;
1620 }else{
1621 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1622 if((nlist_tail = nlist) != NULL)
1623 while(nlist_tail->n_flink != NULL)
1624 nlist_tail = nlist_tail->n_flink;
1627 NYD_LEAVE;
1628 return nlist;
1631 FL struct name *
1632 elide(struct name *names)
1634 size_t i, j, k;
1635 struct name *nlist, *np, **nparr;
1636 NYD_ENTER;
1638 nlist = NULL;
1640 if(names == NULL)
1641 goto jleave;
1643 /* Throw away all deleted nodes */
1644 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1645 if(!(names->n_type & GDEL)){
1646 names->n_blink = np;
1647 if(np != NULL)
1648 np->n_flink = names;
1649 else
1650 nlist = names;
1651 np = names;
1652 ++i;
1654 if(nlist == NULL || i == 1)
1655 goto jleave;
1656 np->n_flink = NULL;
1658 /* Create a temporay array and sort that */
1659 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1661 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1662 nparr[i++] = np;
1664 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1666 /* Remove duplicates XXX speedup, or list_uniq()! */
1667 for(j = 0, --i; j < i;){
1668 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1669 ++j;
1670 else{
1671 for(; k < i; ++k)
1672 nparr[k] = nparr[k + 1];
1673 --i;
1677 /* Throw away all list members which are not part of the array.
1678 * Note this keeps the original, possibly carefully crafted, order of the
1679 * addressees, thus */
1680 for(np = nlist; np != NULL; np = np->n_flink){
1681 for(j = 0; j <= i; ++j)
1682 if(np == nparr[j]){
1683 nparr[j] = NULL;
1684 goto jiter;
1686 /* Drop it */
1687 if(np == nlist){
1688 nlist = np->n_flink;
1689 np->n_blink = NULL;
1690 }else
1691 np->n_blink->n_flink = np->n_flink;
1692 if(np->n_flink != NULL)
1693 np->n_flink->n_blink = np->n_blink;
1694 jiter:;
1697 n_lofi_free(nparr);
1698 jleave:
1699 NYD_LEAVE;
1700 return nlist;
1703 FL int
1704 c_alternates(void *vp){
1705 struct a_nag_group *ngp;
1706 char const *varname, *ccp;
1707 char **argv;
1708 NYD_ENTER;
1710 n_pstate_err_no = n_ERR_NONE;
1712 argv = vp;
1713 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1715 if(*argv == NULL){
1716 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1717 vp = NULL;
1718 }else{
1719 if(varname != NULL)
1720 n_err(_("`alternates': `vput' only supported for show mode\n"));
1722 /* Delete the old set to "declare a list", if *posix* */
1723 if(ok_blook(posix))
1724 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1726 while((ccp = *argv++) != NULL){
1727 size_t l;
1728 struct name *np;
1730 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1731 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1732 n_err(_("Invalid `alternates' argument: %s\n"),
1733 n_shexp_quote_cp(ccp, FAL0));
1734 n_pstate_err_no = n_ERR_INVAL;
1735 vp = NULL;
1736 continue;
1738 ccp = np->n_name;
1740 l = strlen(ccp) +1;
1741 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1742 n_err(_("Failed to create storage for alternates: %s\n"),
1743 n_shexp_quote_cp(ccp, FAL0));
1744 n_pstate_err_no = n_ERR_NOMEM;
1745 vp = NULL;
1749 NYD_LEAVE;
1750 return (vp != NULL ? 0 : 1);
1753 FL int
1754 c_unalternates(void *vp){
1755 char **argv;
1756 int rv;
1757 NYD_ENTER;
1759 rv = 0;
1760 argv = vp;
1762 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1763 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1764 rv = 1;
1765 }while(*++argv != NULL);
1766 NYD_LEAVE;
1767 return rv;
1770 FL struct name *
1771 n_alternates_remove(struct name *np, bool_t keep_single){
1772 /* XXX keep a single pointer, initial null, and immediate remove nodes
1773 * XXX on successful match unless keep single and that pointer null! */
1774 struct a_nag_group_lookup ngl;
1775 struct a_nag_group *ngp;
1776 struct name *xp, *newnp;
1777 NYD_ENTER;
1779 /* Delete the temporary bit from all */
1780 for(xp = np; xp != NULL; xp = xp->n_flink)
1781 xp->n_flags &= ~(ui32_t)SI32_MIN;
1783 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1784 * and hash-lookup alternate instead (unless *allnet*) */
1785 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1786 ngp = a_nag_group_go_next(&ngl))
1787 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1789 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1791 if((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL){
1792 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1793 * TODO At assignment time, as VIP var? */
1795 np = a_nag_namelist_mark_name(np, xp->n_name);
1796 while((xp = xp->n_flink) != NULL);
1797 }else for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1798 xp = xp->n_flink)
1799 np = a_nag_namelist_mark_name(np, xp->n_name);
1801 /* C99 */{
1802 char const *v15compat;
1804 if((v15compat = ok_vlook(replyto)) != NULL){
1805 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1806 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1807 xp = xp->n_flink)
1808 np = a_nag_namelist_mark_name(np, xp->n_name);
1812 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1813 xp = xp->n_flink)
1814 np = a_nag_namelist_mark_name(np, xp->n_name);
1816 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1817 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1818 if(np->n_type & GDEL)
1819 continue;
1820 if(np->n_flags & (ui32_t)SI32_MIN){
1821 if(!keep_single)
1822 continue;
1823 keep_single = FAL0;
1826 np->n_blink = xp;
1827 if(xp != NULL)
1828 xp->n_flink = np;
1829 else
1830 newnp = np;
1831 xp = np;
1832 xp->n_flags &= ~(ui32_t)SI32_MIN;
1834 if(xp != NULL)
1835 xp->n_flink = NULL;
1836 np = newnp;
1838 NYD_LEAVE;
1839 return np;
1842 FL bool_t
1843 n_is_myname(char const *name){
1844 struct a_nag_group_lookup ngl;
1845 struct a_nag_group *ngp;
1846 struct name *xp;
1847 NYD_ENTER;
1849 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1850 goto jleave;
1852 if(!ok_blook(allnet)){
1853 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1854 goto jleave;
1855 }else{
1856 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1857 ngp = a_nag_group_go_next(&ngl))
1858 if(a_nag_is_same_name(ngp->ng_id, name))
1859 goto jleave;
1862 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1863 xp = xp->n_flink)
1864 if(a_nag_is_same_name(xp->n_name, name))
1865 goto jleave;
1867 /* C99 */{
1868 char const *v15compat;
1870 if((v15compat = ok_vlook(replyto)) != NULL){
1871 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1872 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1873 xp = xp->n_flink)
1874 if(a_nag_is_same_name(xp->n_name, name))
1875 goto jleave;
1879 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1880 xp = xp->n_flink)
1881 if(a_nag_is_same_name(xp->n_name, name))
1882 goto jleave;
1884 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1885 xp = xp->n_flink)
1886 if(a_nag_is_same_name(xp->n_name, name))
1887 goto jleave;
1889 name = NULL;
1890 jleave:
1891 NYD_LEAVE;
1892 return (name != NULL);
1895 FL int
1896 c_addrcodec(void *vp){
1897 struct n_addrguts ag;
1898 struct str trims;
1899 struct n_string s_b, *sp;
1900 size_t alen;
1901 int mode;
1902 char const **argv, *varname, *act, *cp;
1903 NYD_ENTER;
1905 sp = n_string_creat_auto(&s_b);
1906 argv = vp;
1907 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1909 act = *argv;
1910 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1912 mode = 0;
1913 if(*act == '+')
1914 mode = 1, ++act;
1915 if(*act == '+')
1916 mode = 2, ++act;
1917 if(*act == '+')
1918 mode = 3, ++act;
1919 if(act >= cp)
1920 goto jesynopsis;
1921 alen = PTR2SIZE(cp - act);
1922 if(*cp != '\0')
1923 ++cp;
1925 trims.l = strlen(trims.s = n_UNCONST(cp));
1926 cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
1927 if(trims.l <= UIZ_MAX / 4)
1928 trims.l <<= 1;
1929 sp = n_string_reserve(sp, trims.l);
1931 n_pstate_err_no = n_ERR_NONE;
1933 if(is_ascncaseprefix(act, "encode", alen)){
1934 /* This function cannot be a simple nalloc() wrapper even later on, since
1935 * we may need to turn any ", () or \ into quoted-pairs */
1936 char c;
1938 while((c = *cp++) != '\0'){
1939 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1940 (c == '\\' && mode < 3))
1941 sp = n_string_push_c(sp, '\\');
1942 sp = n_string_push_c(sp, c);
1945 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1946 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1947 ) != NAME_ADDRSPEC_ISADDR){
1948 cp = sp->s_dat;
1949 n_pstate_err_no = n_ERR_INVAL;
1950 vp = NULL;
1951 }else{
1952 struct name *np;
1954 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1955 cp = np->n_fullname;
1957 }else if(mode == 0){
1958 if(is_ascncaseprefix(act, "decode", alen)){
1959 char c;
1961 while((c = *cp++) != '\0'){
1962 switch(c){
1963 case '(':
1964 sp = n_string_push_c(sp, '(');
1965 act = skip_comment(cp);
1966 if(--act > cp)
1967 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1968 sp = n_string_push_c(sp, ')');
1969 cp = ++act;
1970 break;
1971 case '"':
1972 while(*cp != '\0'){
1973 if((c = *cp++) == '"')
1974 break;
1975 if(c == '\\' && (c = *cp) != '\0')
1976 ++cp;
1977 sp = n_string_push_c(sp, c);
1979 break;
1980 default:
1981 if(c == '\\' && (c = *cp++) == '\0')
1982 break;
1983 sp = n_string_push_c(sp, c);
1984 break;
1987 cp = n_string_cp(sp);
1988 }else if(is_ascncaseprefix(act, "skin", alen) ||
1989 (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
1990 /* Let's just use the is-single-address hack for this one, too.. */
1991 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1992 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1993 ) != NAME_ADDRSPEC_ISADDR){
1994 n_pstate_err_no = n_ERR_INVAL;
1995 vp = NULL;
1996 }else{
1997 struct name *np;
1999 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
2000 cp = np->n_name;
2002 if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
2003 n_pstate_err_no = n_ERR_EXIST;
2005 }else
2006 goto jesynopsis;
2007 }else
2008 goto jesynopsis;
2010 if(varname == NULL){
2011 if(fprintf(n_stdout, "%s\n", cp) < 0){
2012 n_pstate_err_no = n_err_no;
2013 vp = NULL;
2015 }else if(!n_var_vset(varname, (uintptr_t)cp)){
2016 n_pstate_err_no = n_ERR_NOTSUP;
2017 vp = NULL;
2020 jleave:
2021 NYD_LEAVE;
2022 return (vp != NULL ? 0 : 1);
2023 jesynopsis:
2024 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
2025 "<rest-of-line>\n"));
2026 n_pstate_err_no = n_ERR_INVAL;
2027 vp = NULL;
2028 goto jleave;
2031 FL int
2032 c_commandalias(void *vp){
2033 struct a_nag_group *ngp;
2034 char const **argv, *ccp;
2035 int rv;
2036 NYD_ENTER;
2038 rv = 0;
2039 argv = vp;
2041 if((ccp = *argv) == NULL){
2042 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
2043 goto jleave;
2046 /* Verify the name is a valid one, and not a command modifier.
2047 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2048 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
2049 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
2050 !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
2051 !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
2052 n_err(_("`commandalias': not a valid command name: %s\n"),
2053 n_shexp_quote_cp(ccp, FAL0));
2054 rv = 1;
2055 goto jleave;
2058 if(argv[1] == NULL){
2059 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
2060 a_nag_group_print(ngp, n_stdout, NULL);
2061 else{
2062 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
2063 rv = 1;
2065 }else{
2066 /* Because one hardly ever redefines, anything is stored in one chunk */
2067 char *cp;
2068 size_t i, len;
2070 /* Delete the old one, if any; don't get fooled to remove them all */
2071 if(ccp[0] != '*' || ccp[1] != '\0')
2072 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2074 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2075 len += strlen(argv[i]) + 1;
2076 if(len == 0)
2077 len = 1;
2079 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2080 n_err(_("Failed to create storage for commandalias: %s\n"),
2081 n_shexp_quote_cp(ccp, FAL0));
2082 rv = 1;
2083 }else{
2084 struct a_nag_cmd_alias *ncap;
2086 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2087 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2088 cp += sizeof *ncap;
2089 ncap->nca_expand.s = cp;
2090 ncap->nca_expand.l = len - 1;
2092 for(len = 0; (ccp = *argv++) != NULL;)
2093 if((i = strlen(ccp)) > 0){
2094 if(len++ != 0)
2095 *cp++ = ' ';
2096 memcpy(cp, ccp, i);
2097 cp += i;
2099 *cp = '\0';
2102 jleave:
2103 NYD_LEAVE;
2104 return rv;
2107 FL int
2108 c_uncommandalias(void *vp){
2109 char **argv;
2110 int rv;
2111 NYD_ENTER;
2113 rv = 0;
2114 argv = vp;
2116 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2117 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2118 rv = 1;
2119 }while(*++argv != NULL);
2120 NYD_LEAVE;
2121 return rv;
2124 FL char const *
2125 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2126 struct a_nag_group *ngp;
2127 NYD_ENTER;
2129 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2130 name = ngp->ng_id;
2132 if(expansion_or_null != NULL){
2133 struct a_nag_cmd_alias *ncap;
2135 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2136 *expansion_or_null = &ncap->nca_expand;
2138 }else
2139 name = NULL;
2140 NYD_LEAVE;
2141 return name;
2144 FL bool_t
2145 n_alias_is_valid_name(char const *name){
2146 char c;
2147 char const *cp;
2148 bool_t rv;
2149 NYD2_ENTER;
2151 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2152 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2153 * i.e., [[:alnum:]_#:@.-]+$?.
2154 * As extensions allow high-bit bytes, semicolon and period. */
2155 /* TODO n_alias_is_valid_name(): locale dependent validity check,
2156 * TODO with Unicode prefix valid UTF-8! */
2157 if(!alnumchar(c) && c != '_' && c != '-' &&
2158 c != '#' && c != ':' && c != '@' &&
2159 !((ui8_t)c & 0x80) && c != '!' && c != '.'){
2160 if(c == '$' && cp != name && *cp == '\0')
2161 break;
2162 rv = FAL0;
2163 break;
2165 NYD2_LEAVE;
2166 return rv;
2169 FL int
2170 c_alias(void *v)
2172 char const *ecp;
2173 char **argv;
2174 struct a_nag_group *ngp;
2175 int rv;
2176 NYD_ENTER;
2178 rv = 0;
2179 argv = v;
2180 n_UNINIT(ecp, NULL);
2182 if(*argv == NULL)
2183 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2184 else if(!n_alias_is_valid_name(*argv)){
2185 ecp = N_("Not a valid alias name: %s\n");
2186 goto jerr;
2187 }else if(argv[1] == NULL){
2188 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2189 a_nag_group_print(ngp, n_stdout, NULL);
2190 else{
2191 ecp = N_("No such alias: %s\n");
2192 goto jerr;
2194 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2195 ecp = N_("Failed to create alias storage for: %s\n");
2196 jerr:
2197 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2198 rv = 1;
2199 }else{
2200 struct a_nag_grp_names *ngnp_tail, *ngnp;
2201 struct a_nag_grp_names_head *ngnhp;
2203 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2205 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2206 while((ngnp = ngnp_tail->ngn_next) != NULL)
2207 ngnp_tail = ngnp;
2209 for(++argv; *argv != NULL; ++argv){
2210 size_t i;
2212 i = strlen(*argv) +1;
2213 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2214 if(ngnp_tail != NULL)
2215 ngnp_tail->ngn_next = ngnp;
2216 else
2217 ngnhp->ngnh_head = ngnp;
2218 ngnp_tail = ngnp;
2219 ngnp->ngn_next = NULL;
2220 memcpy(ngnp->ngn_id, *argv, i);
2223 NYD_LEAVE;
2224 return rv;
2227 FL int
2228 c_unalias(void *v){
2229 char **argv;
2230 int rv;
2231 NYD_ENTER;
2233 rv = 0;
2234 argv = v;
2236 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2237 n_err(_("No such alias: %s\n"), *argv);
2238 rv = 1;
2239 }while(*++argv != NULL);
2240 NYD_LEAVE;
2241 return rv;
2244 FL int
2245 c_mlist(void *v){
2246 int rv;
2247 NYD_ENTER;
2249 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2250 NYD_LEAVE;
2251 return rv;
2254 FL int
2255 c_unmlist(void *v){
2256 int rv;
2257 NYD_ENTER;
2259 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2260 NYD_LEAVE;
2261 return rv;
2264 FL int
2265 c_mlsubscribe(void *v){
2266 int rv;
2267 NYD_ENTER;
2269 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2270 NYD_LEAVE;
2271 return rv;
2274 FL int
2275 c_unmlsubscribe(void *v){
2276 int rv;
2277 NYD_ENTER;
2279 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2280 NYD_LEAVE;
2281 return rv;
2284 FL enum mlist_state
2285 is_mlist(char const *name, bool_t subscribed_only){
2286 struct a_nag_group *ngp;
2287 #ifdef HAVE_REGEX
2288 struct a_nag_grp_regex **lpp, *ngrp;
2289 bool_t re2;
2290 #endif
2291 enum mlist_state rv;
2292 NYD_ENTER;
2294 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2295 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2297 if(rv == MLIST_KNOWN){
2298 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2299 rv = MLIST_SUBSCRIBED;
2300 else if(subscribed_only)
2301 rv = MLIST_OTHER;
2302 /* Of course, if that is a regular expression it doesn't mean a thing */
2303 #ifdef HAVE_REGEX
2304 if(ngp->ng_type & a_NAG_T_REGEX)
2305 rv = MLIST_OTHER;
2306 else
2307 #endif
2308 goto jleave;
2311 /* Not in the hashmap (as something matchable), walk the lists */
2312 #ifdef HAVE_REGEX
2313 re2 = FAL0;
2314 lpp = &a_nag_mlsub_regex;
2316 jregex_redo:
2317 if((ngrp = *lpp) != NULL){
2318 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2319 /* Relink as the head of this list if the hit count of this group is
2320 * >= 25% of the average hit count */
2321 size_t i;
2323 if(!re2)
2324 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2325 else
2326 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2327 i >>= 2;
2329 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2330 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2331 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2332 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2333 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2334 *lpp = ngrp;
2336 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2337 goto jleave;
2338 }while((ngrp = ngrp->ngr_next) != *lpp);
2341 if(!re2 && !subscribed_only){
2342 re2 = TRU1;
2343 lpp = &a_nag_mlist_regex;
2344 goto jregex_redo;
2346 assert(rv == MLIST_OTHER);
2347 #endif /* HAVE_REGEX */
2349 jleave:
2350 NYD_LEAVE;
2351 return rv;
2354 FL enum mlist_state
2355 is_mlist_mp(struct message *mp, enum mlist_state what){
2356 struct name *np;
2357 bool_t cc;
2358 enum mlist_state rv;
2359 NYD_ENTER;
2361 rv = MLIST_OTHER;
2363 cc = FAL0;
2364 np = lextract(hfield1("to", mp), GTO | GSKIN);
2365 jredo:
2366 for(; np != NULL; np = np->n_flink){
2367 switch(is_mlist(np->n_name, FAL0)){
2368 case MLIST_OTHER:
2369 break;
2370 case MLIST_KNOWN:
2371 if(what == MLIST_KNOWN || what == MLIST_OTHER){
2372 if(rv == MLIST_OTHER)
2373 rv = MLIST_KNOWN;
2374 if(what == MLIST_KNOWN)
2375 goto jleave;
2377 break;
2378 case MLIST_SUBSCRIBED:
2379 if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
2380 if(rv != MLIST_SUBSCRIBED)
2381 rv = MLIST_SUBSCRIBED;
2382 goto jleave;
2384 break;
2388 if(!cc){
2389 cc = TRU1;
2390 np = lextract(hfield1("cc", mp), GCC | GSKIN);
2391 goto jredo;
2393 jleave:
2394 NYD_LEAVE;
2395 return rv;
2398 FL int
2399 c_shortcut(void *vp){
2400 struct a_nag_group *ngp;
2401 char **argv;
2402 int rv;
2403 NYD_ENTER;
2405 rv = 0;
2406 argv = vp;
2408 if(*argv == NULL)
2409 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2410 else if(argv[1] == NULL){
2411 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2412 a_nag_group_print(ngp, n_stdout, NULL);
2413 else{
2414 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2415 rv = 1;
2417 }else for(; *argv != NULL; argv += 2){
2418 /* Because one hardly ever redefines, anything is stored in one chunk */
2419 size_t l;
2420 char *cp;
2422 if(argv[1] == NULL){
2423 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2424 rv = 1;
2425 break;
2427 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2428 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2430 l = strlen(argv[1]) +1;
2431 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2432 n_err(_("Failed to create storage for shortcut: %s\n"),
2433 n_shexp_quote_cp(*argv, FAL0));
2434 rv = 1;
2435 }else{
2436 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2437 memcpy(cp, argv[1], l);
2440 NYD_LEAVE;
2441 return rv;
2444 FL int
2445 c_unshortcut(void *vp){
2446 char **argv;
2447 int rv;
2448 NYD_ENTER;
2450 rv = 0;
2451 argv = vp;
2453 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2454 n_err(_("No such shortcut: %s\n"), *argv);
2455 rv = 1;
2456 }while(*++argv != NULL);
2457 NYD_LEAVE;
2458 return rv;
2461 FL char const *
2462 shortcut_expand(char const *str){
2463 struct a_nag_group *ngp;
2464 NYD_ENTER;
2466 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2467 a_NAG_GP_TO_SUBCLASS(str, ngp);
2468 else
2469 str = NULL;
2470 NYD_LEAVE;
2471 return str;
2474 FL int
2475 c_charsetalias(void *vp){
2476 struct a_nag_group *ngp;
2477 char **argv;
2478 int rv;
2479 NYD_ENTER;
2481 rv = 0;
2482 argv = vp;
2484 if(*argv == NULL)
2485 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2486 else if(argv[1] == NULL){
2487 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2488 a_nag_group_print(ngp, n_stdout, NULL);
2489 else{
2490 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2491 rv = 1;
2493 }else for(; *argv != NULL; argv += 2){
2494 /* Because one hardly ever redefines, anything is stored in one chunk */
2495 char *cp;
2496 size_t dstl;
2497 char const *dst, *src;
2499 if((dst = argv[1]) == NULL){
2500 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2501 rv = 1;
2502 break;
2503 }else if((dst = n_iconv_normalize_name(dst)) == NULL){
2504 n_err(_("charsetalias: invalid target charset %s\n"),
2505 n_shexp_quote_cp(argv[1], FAL0));
2506 rv = 1;
2507 continue;
2508 }else if((src = n_iconv_normalize_name(argv[0])) == NULL){
2509 n_err(_("charsetalias: invalid source charset %s\n"),
2510 n_shexp_quote_cp(argv[0], FAL0));
2511 rv = 1;
2512 continue;
2515 /* Delete the old one, if any; don't get fooled to remove them all */
2516 if(src[0] != '*' || src[1] != '\0')
2517 a_nag_group_del(a_NAG_T_CHARSETALIAS, src);
2519 dstl = strlen(dst) +1;
2520 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, src, dstl)) == NULL){
2521 n_err(_("Failed to create storage for charsetalias: %s\n"),
2522 n_shexp_quote_cp(src, FAL0));
2523 rv = 1;
2524 }else{
2525 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2526 memcpy(cp, dst, dstl);
2529 NYD_LEAVE;
2530 return rv;
2533 FL int
2534 c_uncharsetalias(void *vp){
2535 char **argv, *cp;
2536 int rv;
2537 NYD_ENTER;
2539 rv = 0;
2540 argv = vp;
2543 if((cp = n_iconv_normalize_name(*argv)) == NULL ||
2544 !a_nag_group_del(a_NAG_T_CHARSETALIAS, cp)){
2545 n_err(_("No such `charsetalias': %s\n"),
2546 n_shexp_quote_cp(*argv, FAL0));
2547 rv = 1;
2549 }while(*++argv != NULL);
2550 NYD_LEAVE;
2551 return rv;
2554 FL char const *
2555 n_charsetalias_expand(char const *cp){
2556 struct a_nag_group *ngp;
2557 size_t i;
2558 char const *cp_orig;
2559 NYD_ENTER;
2561 cp_orig = cp;
2563 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2564 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2565 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2566 break;
2569 if(cp != cp_orig)
2570 cp = savestr(cp);
2571 NYD_LEAVE;
2572 return cp;
2575 FL int
2576 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2577 struct a_nag_group *ngp;
2578 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2579 int rv;
2580 NYD_ENTER;
2582 rv = 0;
2583 argv = vp;
2585 if(*argv == NULL)
2586 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2587 else if(argv[1] == NULL){
2588 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2589 a_nag_group_print(ngp, n_stdout, NULL);
2590 else{
2591 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2592 rv = 1;
2594 }else for(; *argv != NULL; argv += 3){
2595 /* Because one hardly ever redefines, anything is stored in one chunk */
2596 char const *ccp;
2597 char *cp, c;
2598 size_t llc, lsc;
2600 if(argv[1] == NULL || argv[2] == NULL){
2601 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2602 rv = 1;
2603 break;
2606 /* Delete the old one, if any; don't get fooled to remove them all */
2607 ccp = argv[0];
2608 if(ccp[0] != '*' || ccp[1] != '\0')
2609 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2611 /* Lowercase it all (for display purposes) */
2612 cp = savestr(ccp);
2613 ccp = cp;
2614 while((c = *cp) != '\0')
2615 *cp++ = lowerconv(c);
2617 llc = strlen(argv[1]) +1;
2618 lsc = strlen(argv[2]) +1;
2619 if(UIZ_MAX - llc <= lsc)
2620 goto jenomem;
2622 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2623 jenomem:
2624 n_err(_("Failed to create storage for filetype: %s\n"),
2625 n_shexp_quote_cp(argv[0], FAL0));
2626 rv = 1;
2627 }else{
2628 struct a_nag_file_type *nftp;
2630 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2631 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2632 cp += sizeof *nftp;
2633 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2634 cp += llc;
2635 nftp->nft_load.l = --llc;
2636 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2637 /*cp += lsc;*/
2638 nftp->nft_save.l = --lsc;
2641 NYD_LEAVE;
2642 return rv;
2645 FL int
2646 c_unfiletype(void *vp){
2647 char **argv;
2648 int rv;
2649 NYD_ENTER;
2651 rv = 0;
2652 argv = vp;
2654 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2655 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2656 rv = 1;
2657 }while(*++argv != NULL);
2658 NYD_LEAVE;
2659 return rv;
2662 FL bool_t
2663 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2664 struct stat stb;
2665 struct a_nag_group_lookup ngl;
2666 struct n_string s, *sp;
2667 struct a_nag_group const *ngp;
2668 ui32_t l;
2669 NYD2_ENTER;
2671 sp = n_string_creat_auto(&s);
2672 sp = n_string_assign_cp(sp, file);
2673 sp = n_string_push_c(sp, '.');
2674 l = sp->s_len;
2676 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2677 ngp = a_nag_group_go_next(&ngl)){
2678 sp = n_string_trunc(sp, l);
2679 sp = n_string_push_buf(sp, ngp->ng_id,
2680 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2682 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2683 if(res_or_null != NULL){
2684 struct a_nag_file_type *nftp;
2686 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2687 res_or_null->ft_ext_dat = ngp->ng_id;
2688 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2689 res_or_null->ft_load_dat = nftp->nft_load.s;
2690 res_or_null->ft_load_len = nftp->nft_load.l;
2691 res_or_null->ft_save_dat = nftp->nft_save.s;
2692 res_or_null->ft_save_len = nftp->nft_save.l;
2694 goto jleave; /* TODO after v15 legacy drop: break; */
2698 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2699 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2700 ngp = (struct a_nag_group*)0x1;
2702 sp = n_string_trunc(sp, l);
2703 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2704 a_nag_OBSOLETE_xz.ft_ext_len);
2705 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2706 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2707 if(res_or_null != NULL)
2708 *res_or_null = a_nag_OBSOLETE_xz;
2709 goto jleave;
2712 sp = n_string_trunc(sp, l);
2713 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2714 a_nag_OBSOLETE_gz.ft_ext_len);
2715 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2716 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2717 if(res_or_null != NULL)
2718 *res_or_null = a_nag_OBSOLETE_gz;
2719 goto jleave;
2722 sp = n_string_trunc(sp, l);
2723 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2724 a_nag_OBSOLETE_bz2.ft_ext_len);
2725 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2726 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2727 if(res_or_null != NULL)
2728 *res_or_null = a_nag_OBSOLETE_bz2;
2729 goto jleave;
2732 ngp = NULL;
2734 jleave:
2735 NYD2_LEAVE;
2736 return (ngp != NULL);
2739 FL bool_t
2740 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2741 char const *ext, *lext;
2742 NYD2_ENTER;
2744 if((ext = strrchr(file, '/')) != NULL)
2745 file = ++ext;
2747 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2748 struct a_nag_group const *ngp;
2750 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2751 lext = ext;
2752 if(res_or_null != NULL){
2753 struct a_nag_file_type *nftp;
2755 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2756 res_or_null->ft_ext_dat = ngp->ng_id;
2757 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2758 res_or_null->ft_load_dat = nftp->nft_load.s;
2759 res_or_null->ft_load_len = nftp->nft_load.l;
2760 res_or_null->ft_save_dat = nftp->nft_save.s;
2761 res_or_null->ft_save_len = nftp->nft_save.l;
2763 goto jleave; /* TODO after v15 legacy drop: break; */
2767 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2768 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2769 if(lext == NULL)
2770 goto jleave;
2772 if(!asccasecmp(lext, "xz")){
2773 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2774 if(res_or_null != NULL)
2775 *res_or_null = a_nag_OBSOLETE_xz;
2776 goto jleave;
2777 }else if(!asccasecmp(lext, "gz")){
2778 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2779 if(res_or_null != NULL)
2780 *res_or_null = a_nag_OBSOLETE_gz;
2781 goto jleave;
2782 }else if(!asccasecmp(lext, "bz2")){
2783 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2784 if(res_or_null != NULL)
2785 *res_or_null = a_nag_OBSOLETE_bz2;
2786 goto jleave;
2787 }else{
2788 char const *cload, *csave;
2789 char *vbuf;
2790 size_t l;
2792 #undef a_X1
2793 #define a_X1 "file-hook-load-"
2794 #undef a_X2
2795 #define a_X2 "file-hook-save-"
2796 l = strlen(lext);
2797 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2799 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2800 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2801 vbuf[sizeof(a_X1) -1 + l] = '\0';
2802 cload = n_var_vlook(vbuf, FAL0);
2804 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2805 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2806 vbuf[sizeof(a_X2) -1 + l] = '\0';
2807 csave = n_var_vlook(vbuf, FAL0);
2809 #undef a_X2
2810 #undef a_X1
2811 n_lofi_free(vbuf);
2813 if((csave != NULL) | (cload != NULL)){
2814 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2815 "please use the `filetype' command");
2817 if(((csave != NULL) ^ (cload != NULL)) == 0){
2818 if(res_or_null != NULL){
2819 res_or_null->ft_ext_dat = lext;
2820 res_or_null->ft_ext_len = l;
2821 res_or_null->ft_load_dat = cload;
2822 res_or_null->ft_load_len = strlen(cload);
2823 res_or_null->ft_save_dat = csave;
2824 res_or_null->ft_save_len = strlen(csave);
2826 goto jleave;
2827 }else
2828 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2829 lext);
2833 lext = NULL;
2835 jleave:
2836 NYD2_LEAVE;
2837 return (lext != NULL);
2840 /* s-it-mode */