1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	Read presentation sequence file
6  */
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include "config.h"
12 #include "global.h"
13 #include "db.h"
14 #include "debug.h"
15 #include "macro.h"
16 #include "nn.h"
17 #include "sequence.h"
18 #include "nn_term.h"
19 #include "variable.h"
20 
21 /* sequence.c */
22 
23 static int      enter_sequence(int mode, group_header * gh);
24 static void     faked_entry(char *name, flag_type flag);
25 static void     end_sequence(void);
26 static int      visit_presentation_file(char *directory, char *seqfile, FILE * hook);
27 
28 group_header   *group_sequence;
29 char           *read_mail = NULL;
30 int             also_subgroups = 1;
31 int             hex_group_args = 0;
32 
33 static int      seq_break_enabled = 1;	/* !! enabled */
34 static int      ignore_done_flag = 0;	/* % toggle */
35 
36 static group_header *tail_sequence = NULL;
37 static group_header *final_sequence = NULL;
38 
39 static int      gs_more_groups;
40 
41 extern group_header *rc_sequence;
42 
43 int
only_folder_args(char ** args)44 only_folder_args(char **args)
45 {
46     register char  *arg;
47 
48     while ((arg = *args++)) {
49 	if (*arg == '+' || *arg == '~' || *arg == '/')
50 	    continue;
51 	if (file_exist(arg, "fr"))
52 	    continue;
53 	return 0;
54     }
55     return 1;
56 }
57 
58 
59 #define	SHOW_NORMAL	0	/* : put this in at current pos */
60 #define	SHOW_FIRST	1	/* < : show these groups first */
61 #define	SHOW_LAST	2	/* > : show this as late as possible */
62 #define	IGNORE_ALWAYS	3	/* ! : ignore these groups completely */
63 #define IGN_UNLESS_RC	4	/* !:X ignore these groups unless in rc */
64 #define	IGN_UNLESS_NEW	5	/* !:O ignore these groups unless new */
65 #define IGN_UNL_RC_NEW	6	/* !:U ignore unsubscribed */
66 #define	IGN_IF_NEW	7	/* !:N ignore these groups if new */
67 
68 #define	SHOW_MODES	" <>!-?*"
69 
70 static int
enter_sequence(int mode,group_header * gh)71 enter_sequence(int mode, group_header * gh)
72 {
73 
74 #ifdef SEQ_TEST
75     if (Debug & SEQ_TEST && mode != SHOW_NORMAL)
76 	tprintf("SEQ(%c), %s\n", SHOW_MODES[mode], gh->group_name);
77 #endif
78 
79     if (gh->master_flag & M_IGNORE_GROUP)
80 	return 0;
81     if (ignore_done_flag) {
82 	if (gh->group_flag & G_SEQUENCE)
83 	    return 0;
84     } else if (gh->group_flag & G_DONE)
85 	return 0;
86 
87     switch (mode) {
88 	case IGN_UNLESS_NEW:
89 	    if ((gh->group_flag & G_NEW) == 0)
90 		gh->group_flag |= G_DONE;
91 	    return 0;
92 
93 	case IGN_IF_NEW:
94 	    if (gh->group_flag & G_NEW)
95 		gh->group_flag |= G_DONE;
96 	    return 0;
97 
98 	case IGN_UNL_RC_NEW:
99 	    if (gh->group_flag & G_NEW)
100 		return 0;
101 	    if (gh->newsrc_line == NULL || (gh->group_flag & G_UNSUBSCRIBED))
102 		gh->group_flag |= G_DONE;
103 	    return 0;
104 
105 	case IGN_UNLESS_RC:
106 	    if (gh->newsrc_line == NULL || (gh->group_flag & (G_UNSUBSCRIBED | G_NEW)))
107 		gh->group_flag |= G_DONE;
108 	    return 0;
109 
110 	case IGNORE_ALWAYS:
111 	    gh->group_flag |= G_DONE;
112 	    return 0;
113 
114 	default:
115 	    gh->group_flag |= G_DONE;
116 	    break;
117     }
118 
119     gh->group_flag |= G_SEQUENCE;
120 
121     if (gh->master_flag & M_NO_DIRECTORY)
122 	return 0;		/* for nntidy -s */
123 
124     switch (mode) {
125 	case SHOW_FIRST:
126 	    if (tail_sequence) {
127 		gh->next_group = group_sequence;
128 		group_sequence = gh;
129 		break;
130 	    }
131 	    /* FALLTHRU */
132 	case SHOW_NORMAL:
133 	    if (tail_sequence)
134 		tail_sequence->next_group = gh;
135 	    else
136 		group_sequence = gh;
137 	    tail_sequence = gh;
138 	    break;
139 
140 	case SHOW_LAST:
141 	    gh->next_group = final_sequence;
142 	    final_sequence = gh;
143 	    break;
144     }
145     return 1;
146 }
147 
148 
149 static void
faked_entry(char * name,flag_type flag)150 faked_entry(char *name, flag_type flag)
151 {
152     group_header   *gh;
153 
154     gh = newobj(group_header, 1);
155 
156     gh->group_name = name;
157     gh->group_flag = flag | G_FAKED;
158     gh->master_flag = 0;
159 
160     /* "invent" an unread article for read_news */
161     gh->last_article = 1;
162     gh->last_db_article = 2;
163 
164     enter_sequence(SHOW_NORMAL, gh);
165 }
166 
167 static void
end_sequence(void)168 end_sequence(void)
169 {
170     register group_header *gh, *backp;
171     register int    seq_ix;
172 
173     if (tail_sequence)
174 	tail_sequence->next_group = NULL;
175 
176     /* set up backward pointers */
177 
178     backp = NULL;
179     seq_ix = 0;
180     Loop_Groups_Sequence(gh) {
181 	gh->preseq_index = ++seq_ix;
182 	gh->prev_group = backp;
183 	backp = gh;
184     }
185 
186 #ifdef SEQ_DUMP
187     if (Debug & SEQ_DUMP) {
188 	for (gh = group_sequence; gh; gh = gh->next_group)
189 	    tprintf("%s\n", gh->group_name);
190 	tputc(NL);
191 
192 	nn_exit(0);
193     }
194 #endif
195 
196 }
197 
198 
199 #ifdef MAIL_READING
200 static int
mail_check(void)201 mail_check(void)
202 {
203     static group_header mail_group;
204     struct stat     st;
205 
206     if (read_mail == NULL)
207 	return;
208     if (stat(read_mail, &st) < 0)
209 	return;
210     if (st.st_size == 0 || st.st_mtime < st.st_atime)
211 	return;
212 
213     mail_group.group_name = read_mail;
214     gh->group_flag = G_FOLDER | G_MAILBOX | G_FAKED;
215     gh->master_flag = 0;
216 
217     /* "invent" an unread article for read_news */
218     gh->last_article = 1;
219     gh->last_db_article = 2;
220 
221 
222     if (tail_sequence) {
223 	mail_group.next_group = group_sequence;
224 	group_sequence = mail_group;
225     } else
226 	enter_sequence(SHOW_NORMAL, &mail_group);
227 }
228 
229 #endif
230 
231 
232 
233 static int
visit_presentation_file(char * directory,char * seqfile,FILE * hook)234 visit_presentation_file(char *directory, char *seqfile, FILE * hook)
235 {
236     register FILE  *sf;
237     register int    c;
238     register group_header *gh;
239     group_header   *mp_group;
240     char            group[FILENAME], *gname;
241     char            savefile[FILENAME], *dflt_save, *enter_macro;
242     register char  *gp;
243     int             mode, merge_groups;
244 
245     if (gs_more_groups == 0)
246 	return 0;
247 
248     if (hook != NULL)
249 	sf = hook;		/* hook to init file */
250     else if ((sf = open_file(relative(directory, seqfile), OPEN_READ)) == NULL)
251 	return 0;
252 
253 #ifdef SEQ_TEST
254     if (Debug & SEQ_TEST)
255 	tprintf("Sequence file %s/%s\n", directory, seqfile);
256 #endif
257 
258     mode = SHOW_NORMAL;
259     savefile[0] = NUL;
260     dflt_save = NULL;
261 
262     while (gs_more_groups) {
263 
264 	if ((c = getc(sf)) == EOF)
265 	    break;
266 	if (!isascii(c) || isspace(c))
267 	    continue;
268 
269 	switch (c) {
270 	    case '!':
271 		mode = IGNORE_ALWAYS;
272 		if ((c = getc(sf)) == EOF)
273 		    continue;
274 		if (c == '!') {
275 		    if (seq_break_enabled) {
276 			fclose(sf);
277 			return 1;
278 		    }
279 		    mode = SHOW_NORMAL;
280 		    continue;
281 		}
282 		if (c == ':') {
283 		    if ((c = getc(sf)) == EOF)
284 			continue;
285 		    if (!isascii(c) || isspace(c) || !isupper(c))
286 			continue;
287 		    switch (c) {
288 			case 'O':
289 			    mode = IGN_UNLESS_NEW;
290 			    continue;
291 			case 'N':
292 			    mode = IGN_IF_NEW;
293 			    continue;
294 			case 'U':
295 			    mode = IGN_UNL_RC_NEW;
296 			    continue;
297 			case 'X':
298 			    mode = IGN_UNLESS_RC;
299 			    continue;
300 			default:
301 			    /* should give error here */
302 			    mode = SHOW_NORMAL;
303 			    continue;
304 		    }
305 		}
306 		ungetc(c, sf);
307 		continue;
308 
309 	    case '<':
310 		mode = SHOW_FIRST;
311 		continue;
312 
313 	    case '>':
314 		mode = SHOW_LAST;
315 		continue;
316 
317 	    case '%':
318 		ignore_done_flag = !ignore_done_flag;
319 		continue;
320 
321 	    case '@':
322 		seq_break_enabled = 0;
323 		mode = SHOW_NORMAL;
324 		continue;
325 
326 	    case '#':
327 		do
328 		    c = getc(sf);
329 		while (c != EOF && c != NL);
330 		mode = SHOW_NORMAL;
331 		continue;
332 
333 	}
334 
335 	gp = group;
336 	merge_groups = 0;
337 	do {
338 	    *gp++ = c;
339 	    if (c == ',')
340 		merge_groups = 1;
341 	    c = getc(sf);
342 	} while (c != EOF && isascii(c) && !isspace(c));
343 
344 	*gp = NUL;
345 
346 	while (c != EOF && (!isascii(c) || isspace(c)))
347 	    c = getc(sf);
348 	if (c == '+' || c == '~' || c == '/') {
349 	    gp = savefile;
350 	    if (c == '+') {
351 		c = getc(sf);
352 		if (c == EOF || (isascii(c) && isspace(c)))
353 		    goto use_same_savefile;
354 		*gp++ = '+';
355 	    }
356 	    do {
357 		*gp++ = c;
358 		c = getc(sf);
359 	    } while (c != EOF && isascii(c) && !isspace(c));
360 	    *gp = NUL;
361 	    dflt_save = savefile[0] ? copy_str(savefile) : NULL;
362 	} else
363 	    dflt_save = NULL;
364 
365 use_same_savefile:
366 	while (c != EOF && (!isascii(c) || isspace(c)))
367 	    c = getc(sf);
368 	if (c == '(') {
369 	    enter_macro = parse_enter_macro(sf, getc(sf));
370 	} else {
371 	    enter_macro = NULL;
372 	    if (c != EOF)
373 		ungetc(c, sf);
374 	}
375 
376 	mp_group = NULL;
377 	for (gp = group; *gp;) {
378 	    gname = gp;
379 	    if (merge_groups) {
380 		while (*gp && *gp != ',')
381 		    gp++;
382 		if (*gp)
383 		    *gp++ = NUL;
384 	    }
385 	    start_group_search(gname);
386 
387 	    while ((gh = get_group_search())) {
388 		if (merge_groups && gh->group_flag & G_UNSUBSCRIBED)
389 		    continue;
390 
391 		if (!enter_sequence(mode, gh))
392 		    continue;
393 
394 		if (merge_groups) {
395 		    if (mp_group == NULL) {
396 			gh->group_flag |= G_MERGE_HEAD;
397 		    } else {
398 			mp_group->merge_with = gh;
399 			gh->group_flag |= G_MERGE_SUB;
400 		    }
401 		    mp_group = gh;
402 		}
403 		if (gh->save_file == NULL)	/* not set by "save-files" */
404 		    gh->save_file = dflt_save;
405 		if (gh->enter_macro == NULL)	/* not set by "on entry" */
406 		    gh->enter_macro = enter_macro;
407 	    }
408 	    if (!merge_groups)
409 		*gp = NUL;
410 	}
411 	if (merge_groups && mp_group != NULL)
412 	    mp_group->merge_with = NULL;
413 	mode = SHOW_NORMAL;
414     }
415 
416     fclose(sf);
417     return 0;
418 }
419 
420 void
parse_save_files(register FILE * sf)421 parse_save_files(register FILE * sf)
422 {
423     register int    c;
424     register group_header *gh;
425     char            group[FILENAME];
426     char           *savefile = NULL;
427     char            namebuf[FILENAME];
428     register char  *gp;
429 
430     for (;;) {
431 	if ((c = getc(sf)) == EOF)
432 	    break;
433 	if (!isascii(c) || isspace(c))
434 	    continue;
435 	if (c == '#') {
436 	    do
437 		c = getc(sf);
438 	    while (c != EOF && c != NL);
439 	    continue;
440 	}
441 	gp = group;
442 	do {
443 	    *gp++ = c;
444 	    c = getc(sf);
445 	} while (c != EOF && isascii(c) && !isspace(c));
446 	*gp = NUL;
447 
448 	if (strcmp(group, "end") == 0)
449 	    break;
450 
451 	while (c != EOF && (!isascii(c) || isspace(c)))
452 	    c = getc(sf);
453 
454 	gp = namebuf;
455 	do {
456 	    *gp++ = c;
457 	    c = getc(sf);
458 	} while (c != EOF && isascii(c) && !isspace(c));
459 	*gp = NUL;
460 	if (namebuf[0] == NUL)
461 	    break;
462 	if (strcmp(namebuf, "+"))
463 	    savefile = copy_str(namebuf);
464 
465 	start_group_search(group);
466 
467 	while ((gh = get_group_search()))
468 	    gh->save_file = savefile;
469     }
470 }
471 
472 void
named_group_sequence(char ** groups)473 named_group_sequence(char **groups)
474 {
475     register group_header *gh;
476     register char  *group;
477     register char  *value;
478     int             non_vars;
479     int             found, errors, gnum;
480 
481     group_sequence = NULL;
482     also_subgroups = 0;
483 
484     errors = 0;
485     non_vars = 0;
486     while ((group = *groups++)) {
487 	non_vars++;
488 
489 	if (hex_group_args) {
490 	    sscanf(group, "%d", &gnum);
491 	    if (gnum < 0 || gnum >= master.number_of_groups)
492 		continue;
493 	    gh = ACTIVE_GROUP(gnum);
494 	    enter_sequence(SHOW_NORMAL, gh);
495 	    continue;
496 	}
497 	if ((gh = lookup(group))) {
498 	    enter_sequence(SHOW_NORMAL, gh);
499 	    continue;
500 	}
501 	if ((value = strchr(group, '='))) {
502 	    *value++ = NUL;
503 	    set_variable(group, 1, value);
504 	    non_vars--;
505 	    continue;
506 	}
507 	if (*group == '+' || *group == '~' || file_exist(group, "fr")) {
508 	    faked_entry(group, G_FOLDER);
509 	    continue;
510 	}
511 
512 	found = 0;
513 	start_group_search(group);
514 	while ((gh = get_group_search())) {
515 	    found++;
516 	    enter_sequence(SHOW_NORMAL, gh);
517 	}
518 
519 	if (!found) {
520 	    tprintf("Group %s not found\n", group);
521 	    fl;
522 	    errors++;
523 	}
524     }
525 
526     if (non_vars == 0) {
527 	normal_group_sequence();
528 	return;
529     }
530     end_sequence();
531 
532     if (errors)
533 	user_delay(2);
534 
535     return;
536 }
537 
538 FILE           *loc_seq_hook = NULL;	/* sequence in local "init" file */
539 FILE           *glob_seq_hook = NULL;	/* sequence in global "init" file */
540 
541 void
normal_group_sequence(void)542 normal_group_sequence(void)
543 {
544     register group_header *gh;
545 
546     group_sequence = NULL;
547     gs_more_groups = 1;
548 
549     /* visit_p_f returns non-zero if terminated by !! */
550 
551     if (visit_presentation_file(nn_directory, "seq", loc_seq_hook))
552 	goto final;
553 
554     if (visit_presentation_file(lib_directory, "sequence", glob_seq_hook))
555 	goto final;
556 
557     Loop_Groups_Sorted(gh) {
558 	enter_sequence(SHOW_NORMAL, gh);
559     }
560 
561 final:
562     if (final_sequence) {
563 	if (tail_sequence) {
564 	    tail_sequence->next_group = final_sequence;
565 	    tail_sequence = NULL;
566 	} else {
567 	    group_sequence = final_sequence;
568 	}
569     }
570 
571 #ifdef MAIL_READING
572     mail_check();
573 #endif
574 
575     end_sequence();
576 }
577 
578 
579 
580 static char    *gs_group;
581 static int      gs_length, gs_index, gs_mode;
582 static group_header *gs_only_group = NULL;
583 
584 #define GS_PREFIX0	0	/* group (or group*) */
585 #define	GS_PREFIX	1	/* group. */
586 #define	GS_SUFFIX	2	/* .group */
587 #define GS_INFIX	3	/* .group. */
588 #define GS_NEW_GROUP	4	/* new group */
589 #define GS_ALL		5	/* all / . */
590 #define	GS_NEWSRC	6	/* RC */
591 
592 void
start_group_search(char * group)593 start_group_search(char *group)
594 {
595     char           *dot;
596     int             last;
597 
598     gs_index = master.number_of_groups;	/* loop will fail */
599 
600     if ((last = strlen(group) - 1) < 0)
601 	return;
602     if (group[last] == '*')
603 	group[last] = NUL;
604     else if (!also_subgroups && (gs_only_group = lookup(group)) != NULL)
605 	return;
606 
607     gs_index = 0;
608     gs_more_groups = 0;
609     gs_length = 0;
610     gs_group = NULL;
611 
612     if (strcmp(group, "NEW") == 0) {
613 	gs_mode = GS_NEW_GROUP;
614 	return;
615     }
616     if (strncmp(group, "RC", 2) == 0) {
617 	gs_mode = GS_NEWSRC;
618 	gs_only_group = rc_sequence;
619 	gs_more_groups = 1;	/* we just can't know! */
620 
621 	if (group[2] != ':')
622 	    return;
623 	if (isdigit(group[3]))
624 	    gs_index = atoi(group + 3);
625 	else {
626 	    gs_group = group + 3;
627 	    gs_length = strlen(gs_group);
628 	}
629 	return;
630     }
631     if (strcmp(group, "all") == 0 || strcmp(group, ".") == 0) {
632 	gs_mode = GS_ALL;
633 	return;
634     }
635     gs_mode = GS_PREFIX0;
636 
637     if (strncmp(group, "all.", 4) == 0)
638 	group += 3;
639 
640     if (*group == '.')
641 	gs_mode = GS_SUFFIX;
642 
643     if ((dot = strrchr(group, '.')) != NULL && dot != group) {
644 	if (dot[1] == NUL || strcmp(dot + 1, "all") == 0) {
645 	    dot[1] = NUL;
646 	    gs_mode = (gs_mode == GS_SUFFIX) ? GS_INFIX : GS_PREFIX;
647 	}
648     }
649     gs_length = strlen(group);
650     gs_group = group;
651 }
652 
653 #define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, (int)(n)) == 0)
654 
655 group_header   *
get_group_search(void)656 get_group_search(void)
657 {
658     register group_header *gh, **ghp;
659     register int    gsidx = gs_index;	/* cached gs_index (RW) */
660     register int    mstngrps = master.number_of_groups;	/* cached (RO) */
661     register int    gdonemask = (ignore_done_flag ? 0 : G_DONE);
662     register int    tail;
663     register int    gslen = gs_length;	/* cached (RO) */
664     register int    gsmgrps = gs_more_groups;	/* cached (RW) */
665     register char  *ghgrpnm;
666     register char  *gsgrp = gs_group;	/* cached (RO) */
667     register int    gsmode = gs_mode;	/* cached (RO) */
668     register group_header *gsog = gs_only_group;	/* cached (RW) */
669 
670     /* most of start up CPU time goes here */
671     if (gsmode == GS_NEWSRC) {
672 	/* gsgrp is usually "" */
673 
674 #ifdef notdef			/* seemed like a good idea at the time;
675 				 * actually slower */
676 	if (gslen == 0) {	/* common case */
677 	    if (gsidx != 0)
678 		--gs_index;
679 	    gs_only_group = NULL;
680 	    return NULL;
681 	}
682 #endif
683 
684 	do {
685 	    gh = gsog;
686 	    if (gh == NULL)
687 		break;
688 	    if (gsidx && --gsidx == 0)
689 		gsog = NULL;
690 	    else if (gsgrp && gh->group_name_length >= gslen &&
691 		     strncmp(gh->group_name, gsgrp, gslen) == 0) {
692 		gsog = NULL;
693 	    } else
694 		gsog = gh->newsrc_seq;
695 	} while (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP);
696 	gs_only_group = gsog;
697 	gs_index = gsidx;
698 	return gh;
699     }
700     if (gsog != NULL) {
701 	gh = gsog;
702 	gs_only_group = NULL;
703 	if (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP)
704 	    return NULL;
705 	return gh;
706     }
707     for (ghp = &sorted_groups[gsidx]; gsidx < mstngrps; ++ghp) {
708 	gh = *ghp;
709 	gsidx++;
710 	if (gh->group_flag & gdonemask || gh->master_flag & M_IGNORE_GROUP)
711 	    continue;
712 	gsmgrps++;
713 	if ((tail = gh->group_name_length - gslen) < 0)
714 	    continue;
715 
716 	ghgrpnm = gh->group_name;
717 	if (gsmode == GS_PREFIX0) {
718 	    /* the big winner */
719 	    if (((tail = ghgrpnm[gslen]) != NUL && tail != '.') ||
720 		!STREQN(ghgrpnm, gsgrp, gslen))
721 		continue;
722 	} else if (gsmode == GS_PREFIX) {
723 	    if (!STREQN(ghgrpnm, gsgrp, gslen))
724 		continue;
725 	} else
726 	    switch (gsmode) {
727 		case GS_NEW_GROUP:
728 		    if ((gh->group_flag & G_NEW) == 0 || gh->group_flag & G_UNSUBSCRIBED)
729 			continue;
730 		    break;
731 
732 		case GS_SUFFIX:
733 		    if (strcmp(ghgrpnm + tail, gsgrp) != 0)
734 			continue;
735 		    break;
736 
737 		case GS_INFIX:
738 		    nn_exitmsg(1, ".name. notation not supported (yet)");
739 		    break;
740 
741 		case GS_ALL:
742 		    break;
743 	    }
744 	gsmgrps--;
745 	gs_more_groups = gsmgrps;
746 	gs_only_group = gsog;
747 	gs_index = gsidx;
748 	return gh;
749     }
750     gs_more_groups = gsmgrps;
751     gs_only_group = gsog;
752     gs_index = gsidx;
753     return NULL;
754 }
755