1/*
2 *  $Id: rc.c,v 1.53 2018/05/31 20:32:15 tom Exp $
3 *
4 *  rc.c -- routines for processing the configuration file
5 *
6 *  Copyright 2000-2012,2018	Thomas E. Dickey
7 *
8 *  This program is free software; you can redistribute it and/or modify
9 *  it under the terms of the GNU Lesser General Public License, version 2.1
10 *  as published by the Free Software Foundation.
11 *
12 *  This program is distributed in the hope that it will be useful, but
13 *  WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 *  Lesser General Public License for more details.
16 *
17 *  You should have received a copy of the GNU Lesser General Public
18 *  License along with this program; if not, write to
19 *	Free Software Foundation, Inc.
20 *	51 Franklin St., Fifth Floor
21 *	Boston, MA 02110, USA.
22 *
23 *  An earlier version of this program lists as authors
24 *	Savio Lam (lam836@cs.cuhk.hk)
25 */
26
27#include <dialog.h>
28
29#include <dlg_keys.h>
30
31#ifdef HAVE_COLOR
32#include <dlg_colors.h>
33
34/*
35 * For matching color names with color values
36 */
37static const color_names_st color_names[] =
38{
39#ifdef HAVE_USE_DEFAULT_COLORS
40    {"DEFAULT", -1},
41#endif
42    {"BLACK", COLOR_BLACK},
43    {"RED", COLOR_RED},
44    {"GREEN", COLOR_GREEN},
45    {"YELLOW", COLOR_YELLOW},
46    {"BLUE", COLOR_BLUE},
47    {"MAGENTA", COLOR_MAGENTA},
48    {"CYAN", COLOR_CYAN},
49    {"WHITE", COLOR_WHITE},
50};				/* color names */
51#define COLOR_COUNT	(sizeof(color_names) / sizeof(color_names[0]))
52#endif /* HAVE_COLOR */
53
54#define GLOBALRC "/etc/dialogrc"
55#define DIALOGRC ".dialogrc"
56
57/* Types of values */
58#define VAL_INT  0
59#define VAL_STR  1
60#define VAL_BOOL 2
61
62/* Type of line in configuration file */
63typedef enum {
64    LINE_ERROR = -1,
65    LINE_EQUALS,
66    LINE_EMPTY
67} PARSE_LINE;
68
69/* number of configuration variables */
70#define VAR_COUNT        (sizeof(vars) / sizeof(vars_st))
71
72/* check if character is string quoting characters */
73#define isquote(c)       ((c) == '"' || (c) == '\'')
74
75/* get last character of string */
76#define lastch(str)      str[strlen(str)-1]
77
78/*
79 * Configuration variables
80 */
81typedef struct {
82    const char *name;		/* name of configuration variable as in DIALOGRC */
83    void *var;			/* address of actual variable to change */
84    int type;			/* type of value */
85    const char *comment;	/* comment to put in "rc" file */
86} vars_st;
87
88/*
89 * This table should contain only references to dialog_state, since dialog_vars
90 * is reset specially in dialog.c before each widget.
91 */
92static const vars_st vars[] =
93{
94    {"aspect",
95     &dialog_state.aspect_ratio,
96     VAL_INT,
97     "Set aspect-ration."},
98
99    {"separate_widget",
100     &dialog_state.separate_str,
101     VAL_STR,
102     "Set separator (for multiple widgets output)."},
103
104    {"tab_len",
105     &dialog_state.tab_len,
106     VAL_INT,
107     "Set tab-length (for textbox tab-conversion)."},
108
109    {"visit_items",
110     &dialog_state.visit_items,
111     VAL_BOOL,
112     "Make tab-traversal for checklist, etc., include the list."},
113
114#ifdef HAVE_COLOR
115    {"use_shadow",
116     &dialog_state.use_shadow,
117     VAL_BOOL,
118     "Shadow dialog boxes? This also turns on color."},
119
120    {"use_colors",
121     &dialog_state.use_colors,
122     VAL_BOOL,
123     "Turn color support ON or OFF"},
124#endif				/* HAVE_COLOR */
125};				/* vars */
126
127static int
128skip_whitespace(char *str, int n)
129{
130    while (isblank(UCH(str[n])) && str[n] != '\0')
131	n++;
132    return n;
133}
134
135static int
136skip_keyword(char *str, int n)
137{
138    while (isalnum(UCH(str[n])) && str[n] != '\0')
139	n++;
140    return n;
141}
142
143static int
144find_vars(char *name)
145{
146    int result = -1;
147    unsigned i;
148
149    for (i = 0; i < VAR_COUNT; i++) {
150	if (dlg_strcmp(vars[i].name, name) == 0) {
151	    result = (int) i;
152	    break;
153	}
154    }
155    return result;
156}
157
158#ifdef HAVE_COLOR
159static int
160find_color(char *name)
161{
162    int result = -1;
163    int i;
164    int limit = dlg_color_count();
165
166    for (i = 0; i < limit; i++) {
167	if (dlg_strcmp(dlg_color_table[i].name, name) == 0) {
168	    result = i;
169	    break;
170	}
171    }
172    return result;
173}
174
175/*
176 * Convert an attribute to a string representation like this:
177 *
178 * "(foreground,background,highlight)"
179 */
180static char *
181attr_to_str(char *str, int fg, int bg, int hl)
182{
183    int i;
184
185    strcpy(str, "(");
186    /* foreground */
187    for (i = 0; fg != color_names[i].value; i++) ;
188    strcat(str, color_names[i].name);
189    strcat(str, ",");
190
191    /* background */
192    for (i = 0; bg != color_names[i].value; i++) ;
193    strcat(str, color_names[i].name);
194
195    /* highlight */
196    strcat(str, hl ? ",ON)" : ",OFF)");
197
198    return str;
199}
200
201/*
202 * Extract the foreground, background and highlight values from an attribute
203 * represented as a string in one of two forms:
204 *
205 * "(foreground,background,highlight)"
206 " "xxxx_color"
207 */
208static int
209str_to_attr(char *str, int *fg, int *bg, int *hl)
210{
211    int i = 0, get_fg = 1;
212    unsigned j;
213    char tempstr[MAX_LEN + 1], *part;
214    size_t have;
215
216    if (str[0] != '(' || lastch(str) != ')') {
217	if ((i = find_color(str)) >= 0) {
218	    *fg = dlg_color_table[i].fg;
219	    *bg = dlg_color_table[i].bg;
220	    *hl = dlg_color_table[i].hilite;
221	    return 0;
222	}
223	return -1;		/* invalid representation */
224    }
225
226    /* remove the parenthesis */
227    have = strlen(str);
228    if (have > MAX_LEN) {
229	have = MAX_LEN - 1;
230    } else {
231	have -= 2;
232    }
233    memcpy(tempstr, str + 1, have);
234    tempstr[have] = '\0';
235
236    /* get foreground and background */
237
238    while (1) {
239	/* skip white space before fg/bg string */
240	i = skip_whitespace(tempstr, i);
241	if (tempstr[i] == '\0')
242	    return -1;		/* invalid representation */
243	part = tempstr + i;	/* set 'part' to start of fg/bg string */
244
245	/* find end of fg/bg string */
246	while (!isblank(UCH(tempstr[i])) && tempstr[i] != ','
247	       && tempstr[i] != '\0')
248	    i++;
249
250	if (tempstr[i] == '\0')
251	    return -1;		/* invalid representation */
252	else if (isblank(UCH(tempstr[i]))) {	/* not yet ',' */
253	    tempstr[i++] = '\0';
254
255	    /* skip white space before ',' */
256	    i = skip_whitespace(tempstr, i);
257	    if (tempstr[i] != ',')
258		return -1;	/* invalid representation */
259	}
260	tempstr[i++] = '\0';	/* skip the ',' */
261	for (j = 0; j < COLOR_COUNT && dlg_strcmp(part, color_names[j].name);
262	     j++) ;
263	if (j == COLOR_COUNT)	/* invalid color name */
264	    return -1;
265	if (get_fg) {
266	    *fg = color_names[j].value;
267	    get_fg = 0;		/* next we have to get the background */
268	} else {
269	    *bg = color_names[j].value;
270	    break;
271	}
272    }				/* got foreground and background */
273
274    /* get highlight */
275
276    /* skip white space before highlight string */
277    i = skip_whitespace(tempstr, i);
278    if (tempstr[i] == '\0')
279	return -1;		/* invalid representation */
280    part = tempstr + i;		/* set 'part' to start of highlight string */
281
282    /* trim trailing white space from highlight string */
283    i = (int) strlen(part) - 1;
284    while (isblank(UCH(part[i])) && i > 0)
285	i--;
286    part[i + 1] = '\0';
287
288    if (!dlg_strcmp(part, "ON"))
289	*hl = TRUE;
290    else if (!dlg_strcmp(part, "OFF"))
291	*hl = FALSE;
292    else
293	return -1;		/* invalid highlight value */
294
295    return 0;
296}
297#endif /* HAVE_COLOR */
298
299/*
300 * Check if the line begins with a special keyword; if so, return true while
301 * pointing params to its parameters.
302 */
303static int
304begins_with(char *line, const char *keyword, char **params)
305{
306    int i = skip_whitespace(line, 0);
307    int j = skip_keyword(line, i);
308
309    if ((j - i) == (int) strlen(keyword)) {
310	char save = line[j];
311	line[j] = 0;
312	if (!dlg_strcmp(keyword, line + i)) {
313	    *params = line + skip_whitespace(line, j + 1);
314	    return 1;
315	}
316	line[j] = save;
317    }
318
319    return 0;
320}
321
322/*
323 * Parse a line in the configuration file
324 *
325 * Each line is of the form:  "variable = value". On exit, 'var' will contain
326 * the variable name, and 'value' will contain the value string.
327 *
328 * Return values:
329 *
330 * LINE_EMPTY   - line is blank or comment
331 * LINE_EQUALS  - line contains "variable = value"
332 * LINE_ERROR   - syntax error in line
333 */
334static PARSE_LINE
335parse_line(char *line, char **var, char **value)
336{
337    int i = 0;
338
339    /* ignore white space at beginning of line */
340    i = skip_whitespace(line, i);
341
342    if (line[i] == '\0')	/* line is blank */
343	return LINE_EMPTY;
344    else if (line[i] == '#')	/* line is comment */
345	return LINE_EMPTY;
346    else if (line[i] == '=')	/* variable names cannot start with a '=' */
347	return LINE_ERROR;
348
349    /* set 'var' to variable name */
350    *var = line + i++;		/* skip to next character */
351
352    /* find end of variable name */
353    while (!isblank(UCH(line[i])) && line[i] != '=' && line[i] != '\0')
354	i++;
355
356    if (line[i] == '\0')	/* syntax error */
357	return LINE_ERROR;
358    else if (line[i] == '=')
359	line[i++] = '\0';
360    else {
361	line[i++] = '\0';
362
363	/* skip white space before '=' */
364	i = skip_whitespace(line, i);
365
366	if (line[i] != '=')	/* syntax error */
367	    return LINE_ERROR;
368	else
369	    i++;		/* skip the '=' */
370    }
371
372    /* skip white space after '=' */
373    i = skip_whitespace(line, i);
374
375    if (line[i] == '\0')
376	return LINE_ERROR;
377    else
378	*value = line + i;	/* set 'value' to value string */
379
380    /* trim trailing white space from 'value' */
381    i = (int) strlen(*value) - 1;
382    while (isblank(UCH((*value)[i])) && i > 0)
383	i--;
384    (*value)[i + 1] = '\0';
385
386    return LINE_EQUALS;		/* no syntax error in line */
387}
388
389/*
390 * Create the configuration file
391 */
392void
393dlg_create_rc(const char *filename)
394{
395    unsigned i;
396    FILE *rc_file;
397
398    if ((rc_file = fopen(filename, "wt")) == NULL)
399	dlg_exiterr("Error opening file for writing in dlg_create_rc().");
400
401    fprintf(rc_file, "#\n\
402# Run-time configuration file for dialog\n\
403#\n\
404# Automatically generated by \"dialog --create-rc <file>\"\n\
405#\n\
406#\n\
407# Types of values:\n\
408#\n\
409# Number     -  <number>\n\
410# String     -  \"string\"\n\
411# Boolean    -  <ON|OFF>\n"
412#ifdef HAVE_COLOR
413	    "\
414# Attribute  -  (foreground,background,highlight?)\n"
415#endif
416	);
417
418    /* Print an entry for each configuration variable */
419    for (i = 0; i < VAR_COUNT; i++) {
420	fprintf(rc_file, "\n# %s\n", vars[i].comment);
421	switch (vars[i].type) {
422	case VAL_INT:
423	    fprintf(rc_file, "%s = %d\n", vars[i].name,
424		    *((int *) vars[i].var));
425	    break;
426	case VAL_STR:
427	    fprintf(rc_file, "%s = \"%s\"\n", vars[i].name,
428		    (char *) vars[i].var);
429	    break;
430	case VAL_BOOL:
431	    fprintf(rc_file, "%s = %s\n", vars[i].name,
432		    *((bool *) vars[i].var) ? "ON" : "OFF");
433	    break;
434	}
435    }
436#ifdef HAVE_COLOR
437    for (i = 0; i < (unsigned) dlg_color_count(); ++i) {
438	char buffer[MAX_LEN + 1];
439	unsigned j;
440	bool repeat = FALSE;
441
442	fprintf(rc_file, "\n# %s\n", dlg_color_table[i].comment);
443	for (j = 0; j != i; ++j) {
444	    if (dlg_color_table[i].fg == dlg_color_table[j].fg
445		&& dlg_color_table[i].bg == dlg_color_table[j].bg
446		&& dlg_color_table[i].hilite == dlg_color_table[j].hilite) {
447		fprintf(rc_file, "%s = %s\n",
448			dlg_color_table[i].name,
449			dlg_color_table[j].name);
450		repeat = TRUE;
451		break;
452	    }
453	}
454
455	if (!repeat) {
456	    fprintf(rc_file, "%s = %s\n", dlg_color_table[i].name,
457		    attr_to_str(buffer,
458				dlg_color_table[i].fg,
459				dlg_color_table[i].bg,
460				dlg_color_table[i].hilite));
461	}
462    }
463#endif /* HAVE_COLOR */
464    dlg_dump_keys(rc_file);
465
466    (void) fclose(rc_file);
467}
468
469/*
470 * Parse the configuration file and set up variables
471 */
472int
473dlg_parse_rc(void)
474{
475    int i;
476    int l = 1;
477    PARSE_LINE parse;
478    char str[MAX_LEN + 1];
479    char *var;
480    char *value;
481    char *tempptr;
482    int result = 0;
483    FILE *rc_file = 0;
484    char *params;
485
486    /*
487     *  At startup, dialog determines the settings to use as follows:
488     *
489     *  a) if the environment variable $DIALOGRC is set, its value determines
490     *     the name of the configuration file.
491     *
492     *  b) if the file in (a) can't be found, use the file $HOME/.dialogrc
493     *     as the configuration file.
494     *
495     *  c) if the file in (b) can't be found, try using the GLOBALRC file.
496     *     Usually this will be /etc/dialogrc.
497     *
498     *  d) if the file in (c) cannot be found, use the compiled-in defaults.
499     */
500
501    /* try step (a) */
502    if ((tempptr = getenv("DIALOGRC")) != NULL)
503	rc_file = fopen(tempptr, "rt");
504
505    if (rc_file == NULL) {	/* step (a) failed? */
506	/* try step (b) */
507	if ((tempptr = getenv("HOME")) != NULL
508	    && strlen(tempptr) < MAX_LEN - (sizeof(DIALOGRC) + 3)) {
509	    if (tempptr[0] == '\0' || lastch(tempptr) == '/')
510		sprintf(str, "%s%s", tempptr, DIALOGRC);
511	    else
512		sprintf(str, "%s/%s", tempptr, DIALOGRC);
513	    rc_file = fopen(tempptr = str, "rt");
514	}
515    }
516
517    if (rc_file == NULL) {	/* step (b) failed? */
518	/* try step (c) */
519	strcpy(str, GLOBALRC);
520	if ((rc_file = fopen(tempptr = str, "rt")) == NULL)
521	    return 0;		/* step (c) failed, use default values */
522    }
523
524    DLG_TRACE(("# opened rc file \"%s\"\n", tempptr));
525    /* Scan each line and set variables */
526    while ((result == 0) && (fgets(str, MAX_LEN, rc_file) != NULL)) {
527	DLG_TRACE(("#\t%s", str));
528	if (*str == '\0' || lastch(str) != '\n') {
529	    /* ignore rest of file if line too long */
530	    fprintf(stderr, "\nParse error: line %d of configuration"
531		    " file too long.\n", l);
532	    result = -1;	/* parse aborted */
533	    break;
534	}
535
536	lastch(str) = '\0';
537	if (begins_with(str, "bindkey", &params)) {
538	    if (!dlg_parse_bindkey(params)) {
539		fprintf(stderr, "\nParse error: line %d of configuration\n", l);
540		result = -1;
541	    }
542	    continue;
543	}
544	parse = parse_line(str, &var, &value);	/* parse current line */
545
546	switch (parse) {
547	case LINE_EMPTY:	/* ignore blank lines and comments */
548	    break;
549	case LINE_EQUALS:
550	    /* search table for matching config variable name */
551	    if ((i = find_vars(var)) >= 0) {
552		switch (vars[i].type) {
553		case VAL_INT:
554		    *((int *) vars[i].var) = atoi(value);
555		    break;
556		case VAL_STR:
557		    if (!isquote(value[0]) || !isquote(lastch(value))
558			|| strlen(value) < 2) {
559			fprintf(stderr, "\nParse error: string value "
560				"expected at line %d of configuration "
561				"file.\n", l);
562			result = -1;	/* parse aborted */
563		    } else {
564			/* remove the (") quotes */
565			value++;
566			lastch(value) = '\0';
567			strcpy((char *) vars[i].var, value);
568		    }
569		    break;
570		case VAL_BOOL:
571		    if (!dlg_strcmp(value, "ON"))
572			*((bool *) vars[i].var) = TRUE;
573		    else if (!dlg_strcmp(value, "OFF"))
574			*((bool *) vars[i].var) = FALSE;
575		    else {
576			fprintf(stderr, "\nParse error: boolean value "
577				"expected at line %d of configuration "
578				"file (found %s).\n", l, value);
579			result = -1;	/* parse aborted */
580		    }
581		    break;
582		}
583#ifdef HAVE_COLOR
584	    } else if ((i = find_color(var)) >= 0) {
585		int fg = 0;
586		int bg = 0;
587		int hl = 0;
588		if (str_to_attr(value, &fg, &bg, &hl) == -1) {
589		    fprintf(stderr, "\nParse error: attribute "
590			    "value expected at line %d of configuration "
591			    "file.\n", l);
592		    result = -1;	/* parse aborted */
593		} else {
594		    dlg_color_table[i].fg = fg;
595		    dlg_color_table[i].bg = bg;
596		    dlg_color_table[i].hilite = hl;
597		}
598	    } else {
599#endif /* HAVE_COLOR */
600		fprintf(stderr, "\nParse error: unknown variable "
601			"at line %d of configuration file:\n\t%s\n", l, var);
602		result = -1;	/* parse aborted */
603	    }
604	    break;
605	case LINE_ERROR:
606	    fprintf(stderr, "\nParse error: syntax error at line %d of "
607		    "configuration file.\n", l);
608	    result = -1;	/* parse aborted */
609	    break;
610	}
611	l++;			/* next line */
612    }
613
614    (void) fclose(rc_file);
615    return result;
616}
617