xref: /openbsd/lib/libcurses/tinfo/parse_entry.c (revision 379777c0)
1 /* $OpenBSD: parse_entry.c,v 1.14 2023/10/17 09:52:09 nicm Exp $ */
2 
3 /****************************************************************************
4  * Copyright 2018-2022,2023 Thomas E. Dickey                                *
5  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
6  *                                                                          *
7  * Permission is hereby granted, free of charge, to any person obtaining a  *
8  * copy of this software and associated documentation files (the            *
9  * "Software"), to deal in the Software without restriction, including      *
10  * without limitation the rights to use, copy, modify, merge, publish,      *
11  * distribute, distribute with modifications, sublicense, and/or sell       *
12  * copies of the Software, and to permit persons to whom the Software is    *
13  * furnished to do so, subject to the following conditions:                 *
14  *                                                                          *
15  * The above copyright notice and this permission notice shall be included  *
16  * in all copies or substantial portions of the Software.                   *
17  *                                                                          *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
21  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
22  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
23  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
24  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
25  *                                                                          *
26  * Except as contained in this notice, the name(s) of the above copyright   *
27  * holders shall not be used in advertising or otherwise to promote the     *
28  * sale, use or other dealings in this Software without prior written       *
29  * authorization.                                                           *
30  ****************************************************************************/
31 
32 /****************************************************************************
33  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
34  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
35  *     and: Thomas E. Dickey                        1996-on                 *
36  ****************************************************************************/
37 
38 /*
39  *	parse_entry.c -- compile one terminfo or termcap entry
40  *
41  *	Get an exact in-core representation of an entry.  Don't
42  *	try to resolve use or tc capabilities, that is someone
43  *	else's job.  Depends on the lexical analyzer to get tokens
44  *	from the input stream.
45  */
46 
47 #define __INTERNAL_CAPS_VISIBLE
48 #include <curses.priv.h>
49 
50 #include <ctype.h>
51 #include <tic.h>
52 
53 MODULE_ID("$Id: parse_entry.c,v 1.14 2023/10/17 09:52:09 nicm Exp $")
54 
55 #ifdef LINT
56 static short const parametrized[] =
57 {0};
58 #else
59 #include <parametrized.h>
60 #endif
61 
62 static void postprocess_termcap(TERMTYPE2 *, bool);
63 static void postprocess_terminfo(TERMTYPE2 *);
64 static struct name_table_entry const *lookup_fullname(const char *name);
65 
66 #if NCURSES_XNAMES
67 
68 static struct name_table_entry const *
69 _nc_extend_names(ENTRY * entryp, const char *name, int token_type)
70 {
71     static struct name_table_entry temp;
72     TERMTYPE2 *tp = &(entryp->tterm);
73     unsigned offset = 0;
74     unsigned actual;
75     unsigned tindex;
76     unsigned first, last, n;
77     bool found;
78 
79     switch (token_type) {
80     case BOOLEAN:
81 	first = 0;
82 	last = tp->ext_Booleans;
83 	offset = tp->ext_Booleans;
84 	tindex = tp->num_Booleans;
85 	break;
86     case NUMBER:
87 	first = tp->ext_Booleans;
88 	last = tp->ext_Numbers + first;
89 	offset = (unsigned) (tp->ext_Booleans + tp->ext_Numbers);
90 	tindex = tp->num_Numbers;
91 	break;
92     case STRING:
93 	first = (unsigned) (tp->ext_Booleans + tp->ext_Numbers);
94 	last = tp->ext_Strings + first;
95 	offset = (unsigned) (tp->ext_Booleans + tp->ext_Numbers + tp->ext_Strings);
96 	tindex = tp->num_Strings;
97 	break;
98     case CANCEL:
99 	actual = NUM_EXT_NAMES(tp);
100 	for (n = 0; n < actual; n++) {
101 	    if (!strcmp(name, tp->ext_Names[n])) {
102 		if (n > (unsigned) (tp->ext_Booleans + tp->ext_Numbers)) {
103 		    token_type = STRING;
104 		} else if (n > tp->ext_Booleans) {
105 		    token_type = NUMBER;
106 		} else {
107 		    token_type = BOOLEAN;
108 		}
109 		return _nc_extend_names(entryp, name, token_type);
110 	    }
111 	}
112 	/* Well, we are given a cancel for a name that we don't recognize */
113 	return _nc_extend_names(entryp, name, STRING);
114     default:
115 	return NULL;
116     }
117 
118     /* Adjust the 'offset' (insertion-point) to keep the lists of extended
119      * names sorted.
120      */
121     for (n = first, found = FALSE; n < last; n++) {
122 	int cmp = strcmp(tp->ext_Names[n], name);
123 	if (cmp == 0)
124 	    found = TRUE;
125 	if (cmp >= 0) {
126 	    offset = n;
127 	    tindex = n - first;
128 	    switch (token_type) {
129 	    case BOOLEAN:
130 		tindex += BOOLCOUNT;
131 		break;
132 	    case NUMBER:
133 		tindex += NUMCOUNT;
134 		break;
135 	    case STRING:
136 		tindex += STRCOUNT;
137 		break;
138 	    }
139 	    break;
140 	}
141     }
142 
143 #define for_each_value(max) \
144 	for (last = (unsigned) (max - 1); last > tindex; last--)
145 
146     if (!found) {
147 	char *saved;
148 
149 	if ((saved = _nc_save_str(name)) == NULL)
150 	    return NULL;
151 
152 	switch (token_type) {
153 	case BOOLEAN:
154 	    tp->ext_Booleans++;
155 	    tp->num_Booleans++;
156 	    TYPE_REALLOC(NCURSES_SBOOL, tp->num_Booleans, tp->Booleans);
157 	    for_each_value(tp->num_Booleans)
158 		tp->Booleans[last] = tp->Booleans[last - 1];
159 	    break;
160 	case NUMBER:
161 	    tp->ext_Numbers++;
162 	    tp->num_Numbers++;
163 	    TYPE_REALLOC(NCURSES_INT2, tp->num_Numbers, tp->Numbers);
164 	    for_each_value(tp->num_Numbers)
165 		tp->Numbers[last] = tp->Numbers[last - 1];
166 	    break;
167 	case STRING:
168 	    tp->ext_Strings++;
169 	    tp->num_Strings++;
170 	    TYPE_REALLOC(char *, tp->num_Strings, tp->Strings);
171 	    for_each_value(tp->num_Strings)
172 		tp->Strings[last] = tp->Strings[last - 1];
173 	    break;
174 	}
175 	actual = NUM_EXT_NAMES(tp);
176 	TYPE_REALLOC(char *, actual, tp->ext_Names);
177 	while (--actual > offset)
178 	    tp->ext_Names[actual] = tp->ext_Names[actual - 1];
179 	tp->ext_Names[offset] = saved;
180     }
181 
182     temp.nte_name = tp->ext_Names[offset];
183     temp.nte_type = token_type;
184     temp.nte_index = (short) tindex;
185     temp.nte_link = -1;
186 
187     return &temp;
188 }
189 
190 static const char *
191 usertype2s(int mask)
192 {
193     const char *result = "unknown";
194     if (mask & (1 << BOOLEAN)) {
195 	result = "boolean";
196     } else if (mask & (1 << NUMBER)) {
197 	result = "number";
198     } else if (mask & (1 << STRING)) {
199 	result = "string";
200     }
201     return result;
202 }
203 
204 static bool
205 expected_type(const char *name, int token_type, bool silent)
206 {
207     struct user_table_entry const *entry = _nc_find_user_entry(name);
208     bool result = TRUE;
209     if ((entry != 0) && (token_type != CANCEL)) {
210 	int have_type = (1 << token_type);
211 	if (!(entry->ute_type & have_type)) {
212 	    if (!silent)
213 		_nc_warning("expected %s-type for %s, have %s",
214 			    usertype2s(entry->ute_type),
215 			    name,
216 			    usertype2s(have_type));
217 	    result = FALSE;
218 	}
219     }
220     return result;
221 }
222 #endif /* NCURSES_XNAMES */
223 
224 /*
225  * A valid entry name uses characters from the "portable character set"
226  * (more commonly referred to as US-ASCII), and disallows some of the
227  * punctuation characters:
228  *
229  * '/' is a pathname separator
230  * '\' may be a pathname separator, but more important, is an escape
231  * '|' delimits names and description
232  * '#' denotes a numeric value
233  * '=' denotes a string value
234  * '@' denotes a cancelled symbol
235  * ',' separates terminfo capabilities
236  * ':' separates termcap capabilities
237  *
238  * Termcap capability names may begin with a '#' or '@' (since they have
239  * exactly two characters).
240  */
241 static bool
242 valid_entryname(const char *name)
243 {
244     bool result = TRUE;
245     bool first = TRUE;
246     int ch;
247     while ((ch = UChar(*name++)) != '\0') {
248 	if (ch <= ' ' || ch > '~' || strchr("/\\|=,:", ch) != NULL) {
249 	    result = FALSE;
250 	    break;
251 	}
252 	if (!first && strchr("#@", ch) != NULL) {
253 	    result = FALSE;
254 	    break;
255 	}
256 	first = FALSE;
257     }
258     return result;
259 }
260 
261 /*
262  *	int
263  *	_nc_parse_entry(entry, literal, silent)
264  *
265  *	Compile one entry.  Doesn't try to resolve use or tc capabilities.
266  *
267  *	found-forward-use = FALSE
268  *	re-initialise internal arrays
269  *	get_token();
270  *	if the token was not a name in column 1, complain and die
271  *	save names in entry's string table
272  *	while (get_token() is not EOF and not NAMES)
273  *	        check for existence and type-correctness
274  *	        enter cap into structure
275  *	        if STRING
276  *	            save string in entry's string table
277  *	push back token
278  */
279 
280 #define BAD_TC_USAGE if (!bad_tc_usage) \
281  	{ bad_tc_usage = TRUE; \
282 	 _nc_warning("Legacy termcap allows only a trailing tc= clause"); }
283 
284 #define MAX_NUMBER MAX_OF_TYPE(NCURSES_INT2)
285 
286 NCURSES_EXPORT(int)
287 _nc_parse_entry(ENTRY * entryp, int literal, bool silent)
288 {
289     int token_type;
290     struct name_table_entry const *entry_ptr;
291     char *ptr, *base;
292     const char *name;
293     bool bad_tc_usage = FALSE;
294 
295     TR(TRACE_DATABASE,
296        (T_CALLED("_nc_parse_entry(entry=%p, literal=%d, silent=%d)"),
297 	(void *) entryp, literal, silent));
298 
299     token_type = _nc_get_token(silent);
300 
301     if (token_type == EOF)
302 	returnDB(EOF);
303     if (token_type != NAMES)
304 	_nc_err_abort("Entry does not start with terminal names in column one");
305 
306     _nc_init_entry(entryp);
307 
308     entryp->cstart = _nc_comment_start;
309     entryp->cend = _nc_comment_end;
310     entryp->startline = _nc_start_line;
311     DEBUG(2, ("Comment range is %ld to %ld", entryp->cstart, entryp->cend));
312 
313     /*
314      * Strip off the 2-character termcap name, if present.  Originally termcap
315      * used that as an indexing aid.  We can retain 2-character terminfo names,
316      * but note that they would be lost if we translate to/from termcap.  This
317      * feature is supposedly obsolete since "newer" BSD implementations do not
318      * use it; however our reference for this feature is SunOS 4.x, which
319      * implemented it.  Note that the resulting terminal type was never the
320      * 2-character name, but was instead the first alias after that.
321      */
322 #define ok_TC2(s) (isgraph(UChar(s)) && (s) != '|')
323     ptr = _nc_curr_token.tk_name;
324     if (_nc_syntax == SYN_TERMCAP
325 #if NCURSES_XNAMES
326 	&& !_nc_user_definable
327 #endif
328 	) {
329 	if (ok_TC2(ptr[0]) && ok_TC2(ptr[1]) && (ptr[2] == '|')) {
330 	    ptr += 3;
331 	    _nc_curr_token.tk_name[2] = '\0';
332 	}
333     }
334 
335     entryp->tterm.str_table = entryp->tterm.term_names = _nc_save_str(ptr);
336 
337     if (entryp->tterm.str_table == 0)
338 	returnDB(ERR);
339 
340     DEBUG(2, ("Starting '%s'", ptr));
341 
342     /*
343      * We do this because the one-token lookahead in the parse loop
344      * results in the terminal type getting prematurely set to correspond
345      * to that of the next entry.
346      */
347     name = _nc_first_name(entryp->tterm.term_names);
348     if (!valid_entryname(name)) {
349 	_nc_warning("invalid entry name \"%s\"", name);
350 	name = "invalid";
351     }
352     _nc_set_type(name);
353 
354     /* check for overly-long names and aliases */
355     for (base = entryp->tterm.term_names; (ptr = strchr(base, '|')) != 0;
356 	 base = ptr + 1) {
357 	if (ptr - base > MAX_ALIAS) {
358 	    _nc_warning("%s `%.*s' may be too long",
359 			(base == entryp->tterm.term_names)
360 			? "primary name"
361 			: "alias",
362 			(int) (ptr - base), base);
363 	}
364     }
365 
366     entryp->nuses = 0;
367 
368     for (token_type = _nc_get_token(silent);
369 	 token_type != EOF && token_type != NAMES;
370 	 token_type = _nc_get_token(silent)) {
371 	bool is_use = (strcmp(_nc_curr_token.tk_name, "use") == 0);
372 	bool is_tc = !is_use && (strcmp(_nc_curr_token.tk_name, "tc") == 0);
373 	if (is_use || is_tc) {
374 	    char *saved;
375 
376 	    if (!VALID_STRING(_nc_curr_token.tk_valstring)
377 		|| _nc_curr_token.tk_valstring[0] == '\0') {
378 		_nc_warning("missing name for use-clause");
379 		continue;
380 	    } else if (!valid_entryname(_nc_curr_token.tk_valstring)) {
381 		_nc_warning("invalid name for use-clause \"%s\"",
382 			    _nc_curr_token.tk_valstring);
383 		continue;
384 	    } else if (entryp->nuses >= MAX_USES) {
385 		_nc_warning("too many use-clauses, ignored \"%s\"",
386 			    _nc_curr_token.tk_valstring);
387 		continue;
388 	    }
389 	    if ((saved = _nc_save_str(_nc_curr_token.tk_valstring)) != NULL) {
390 		entryp->uses[entryp->nuses].name = saved;
391 		entryp->uses[entryp->nuses].line = _nc_curr_line;
392 		entryp->nuses++;
393 		if (entryp->nuses > 1 && is_tc) {
394 		    BAD_TC_USAGE
395 		}
396 	    }
397 	} else {
398 	    /* normal token lookup */
399 	    entry_ptr = _nc_find_entry(_nc_curr_token.tk_name,
400 				       _nc_get_hash_table(_nc_syntax));
401 
402 	    /*
403 	     * Our kluge to handle aliasing.  The reason it is done
404 	     * this ugly way, with a linear search, is so the hashing
405 	     * machinery doesn't have to be made really complicated
406 	     * (also we get better warnings this way).  No point in
407 	     * making this case fast, aliased caps aren't common now
408 	     * and will get rarer.
409 	     */
410 	    if (entry_ptr == NOTFOUND) {
411 		const struct alias *ap;
412 
413 		if (_nc_syntax == SYN_TERMCAP) {
414 		    if (entryp->nuses != 0) {
415 			BAD_TC_USAGE
416 		    }
417 		    for (ap = _nc_get_alias_table(TRUE); ap->from; ap++)
418 			if (strcmp(ap->from, _nc_curr_token.tk_name) == 0) {
419 			    if (ap->to == (char *) 0) {
420 				_nc_warning("%s (%s termcap extension) ignored",
421 					    ap->from, ap->source);
422 				goto nexttok;
423 			    }
424 
425 			    entry_ptr = _nc_find_entry(ap->to,
426 						       _nc_get_hash_table(TRUE));
427 			    if (entry_ptr && !silent)
428 				_nc_warning("%s (%s termcap extension) aliased to %s",
429 					    ap->from, ap->source, ap->to);
430 			    break;
431 			}
432 		} else {	/* if (_nc_syntax == SYN_TERMINFO) */
433 		    for (ap = _nc_get_alias_table(FALSE); ap->from; ap++)
434 			if (strcmp(ap->from, _nc_curr_token.tk_name) == 0) {
435 			    if (ap->to == (char *) 0) {
436 				_nc_warning("%s (%s terminfo extension) ignored",
437 					    ap->from, ap->source);
438 				goto nexttok;
439 			    }
440 
441 			    entry_ptr = _nc_find_entry(ap->to,
442 						       _nc_get_hash_table(FALSE));
443 			    if (entry_ptr && !silent)
444 				_nc_warning("%s (%s terminfo extension) aliased to %s",
445 					    ap->from, ap->source, ap->to);
446 			    break;
447 			}
448 
449 		    if (entry_ptr == NOTFOUND) {
450 			entry_ptr = lookup_fullname(_nc_curr_token.tk_name);
451 		    }
452 		}
453 	    }
454 #if NCURSES_XNAMES
455 	    /*
456 	     * If we have extended-names active, we will automatically
457 	     * define a name based on its context.
458 	     */
459 	    if (entry_ptr == NOTFOUND
460 		&& _nc_user_definable) {
461 		if (expected_type(_nc_curr_token.tk_name, token_type, silent)) {
462 		    if ((entry_ptr = _nc_extend_names(entryp,
463 						      _nc_curr_token.tk_name,
464 						      token_type)) != 0) {
465 			if (_nc_tracing >= DEBUG_LEVEL(1)) {
466 			    _nc_warning("extended capability '%s'",
467 					_nc_curr_token.tk_name);
468 			}
469 		    }
470 		} else {
471 		    /* ignore it: we have already printed error message */
472 		    continue;
473 		}
474 	    }
475 #endif /* NCURSES_XNAMES */
476 
477 	    /* can't find this cap name, not even as an alias */
478 	    if (entry_ptr == NOTFOUND) {
479 		if (!silent)
480 		    _nc_warning("unknown capability '%s'",
481 				_nc_curr_token.tk_name);
482 		continue;
483 	    }
484 
485 	    /* deal with bad type/value combinations. */
486 	    if (token_type == CANCEL) {
487 		/*
488 		 * Prefer terminfo in this (long-obsolete) ambiguity:
489 		 */
490 		if (!strcmp("ma", _nc_curr_token.tk_name)) {
491 		    entry_ptr = _nc_find_type_entry("ma", NUMBER,
492 						    _nc_syntax != 0);
493 		    assert(entry_ptr != 0);
494 		}
495 	    } else if (entry_ptr->nte_type != token_type) {
496 		/*
497 		 * Nasty special cases here handle situations in which type
498 		 * information can resolve name clashes.  Normal lookup
499 		 * finds the last instance in the capability table of a
500 		 * given name, regardless of type.  find_type_entry looks
501 		 * for a first matching instance with given type.  So as
502 		 * long as all ambiguous names occur in pairs of distinct
503 		 * type, this will do the job.
504 		 */
505 
506 		if (token_type == NUMBER
507 		    && !strcmp("ma", _nc_curr_token.tk_name)) {
508 		    /* tell max_attributes from arrow_key_map */
509 		    entry_ptr = _nc_find_type_entry("ma", NUMBER,
510 						    _nc_syntax != 0);
511 		    assert(entry_ptr != 0);
512 
513 		} else if (token_type == STRING
514 			   && !strcmp("MT", _nc_curr_token.tk_name)) {
515 		    /* map terminfo's string MT to MT */
516 		    entry_ptr = _nc_find_type_entry("MT", STRING,
517 						    _nc_syntax != 0);
518 		    assert(entry_ptr != 0);
519 
520 		} else if (token_type == BOOLEAN
521 			   && entry_ptr->nte_type == STRING) {
522 		    /* treat strings without following "=" as empty strings */
523 		    token_type = STRING;
524 		} else {
525 		    /* we couldn't recover; skip this token */
526 		    if (!silent) {
527 			const char *type_name;
528 			switch (entry_ptr->nte_type) {
529 			case BOOLEAN:
530 			    type_name = "boolean";
531 			    break;
532 			case STRING:
533 			    type_name = "string";
534 			    break;
535 			case NUMBER:
536 			    type_name = "numeric";
537 			    break;
538 			default:
539 			    type_name = "unknown";
540 			    break;
541 			}
542 			_nc_warning("wrong type used for %s capability '%s'",
543 				    type_name, _nc_curr_token.tk_name);
544 		    }
545 		    continue;
546 		}
547 	    }
548 
549 	    /* now we know that the type/value combination is OK */
550 	    switch (token_type) {
551 	    case CANCEL:
552 		switch (entry_ptr->nte_type) {
553 		case BOOLEAN:
554 		    entryp->tterm.Booleans[entry_ptr->nte_index] = CANCELLED_BOOLEAN;
555 		    break;
556 
557 		case NUMBER:
558 		    entryp->tterm.Numbers[entry_ptr->nte_index] = CANCELLED_NUMERIC;
559 		    break;
560 
561 		case STRING:
562 		    entryp->tterm.Strings[entry_ptr->nte_index] = CANCELLED_STRING;
563 		    break;
564 		}
565 		break;
566 
567 	    case BOOLEAN:
568 		entryp->tterm.Booleans[entry_ptr->nte_index] = TRUE;
569 		break;
570 
571 	    case NUMBER:
572 #if !NCURSES_EXT_NUMBERS
573 		if (_nc_curr_token.tk_valnumber > MAX_NUMBER) {
574 		    entryp->tterm.Numbers[entry_ptr->nte_index] = MAX_NUMBER;
575 		} else
576 #endif
577 		{
578 		    entryp->tterm.Numbers[entry_ptr->nte_index] =
579 			(NCURSES_INT2) _nc_curr_token.tk_valnumber;
580 		}
581 		break;
582 
583 	    case STRING:
584 		ptr = _nc_curr_token.tk_valstring;
585 		if (_nc_syntax == SYN_TERMCAP) {
586 		    int n = entry_ptr->nte_index;
587 		    ptr = _nc_captoinfo(_nc_curr_token.tk_name,
588 					ptr,
589 					(n < (int) SIZEOF(parametrized))
590 					? parametrized[n]
591 					: 0);
592 		}
593 		entryp->tterm.Strings[entry_ptr->nte_index] = _nc_save_str(ptr);
594 		break;
595 
596 	    default:
597 		if (!silent)
598 		    _nc_warning("unknown token type");
599 		_nc_panic_mode((char) ((_nc_syntax == SYN_TERMCAP) ? ':' : ','));
600 		continue;
601 	    }
602 	}			/* end else cur_token.name != "use" */
603       nexttok:
604 	continue;		/* cannot have a label w/o statement */
605     }				/* endwhile (not EOF and not NAMES) */
606 
607     _nc_push_token(token_type);
608     _nc_set_type(_nc_first_name(entryp->tterm.term_names));
609 
610     /*
611      * Try to deduce as much as possible from extension capabilities
612      * (this includes obsolete BSD capabilities).  Sigh...it would be more
613      * space-efficient to call this after use resolution, but it has
614      * to be done before entry allocation is wrapped up.
615      */
616     if (!literal) {
617 	if (_nc_syntax == SYN_TERMCAP) {
618 	    bool has_base_entry = FALSE;
619 
620 	    /*
621 	     * Don't insert defaults if this is a `+' entry meant only
622 	     * for inclusion in other entries (not sure termcap ever
623 	     * had these, actually).
624 	     */
625 	    if (strchr(entryp->tterm.term_names, '+')) {
626 		has_base_entry = TRUE;
627 	    } else {
628 		unsigned i;
629 		/*
630 		 * Otherwise, look for a base entry that will already
631 		 * have picked up defaults via translation.
632 		 */
633 		for (i = 0; i < entryp->nuses; i++) {
634 		    if (entryp->uses[i].name != 0
635 			&& !strchr(entryp->uses[i].name, '+'))
636 			has_base_entry = TRUE;
637 		}
638 	    }
639 
640 	    postprocess_termcap(&entryp->tterm, has_base_entry);
641 	} else
642 	    postprocess_terminfo(&entryp->tterm);
643     }
644     _nc_wrap_entry(entryp, FALSE);
645 
646     returnDB(OK);
647 }
648 
649 NCURSES_EXPORT(int)
650 _nc_capcmp(const char *s, const char *t)
651 /* compare two string capabilities, stripping out padding */
652 {
653     bool ok_s = VALID_STRING(s);
654     bool ok_t = VALID_STRING(t);
655 
656     if (ok_s && ok_t) {
657 	for (;;) {
658 	    if (s[0] == '$' && s[1] == '<') {
659 		for (s += 2;; s++) {
660 		    if (!(isdigit(UChar(*s))
661 			  || *s == '.'
662 			  || *s == '*'
663 			  || *s == '/'
664 			  || *s == '>')) {
665 			break;
666 		    }
667 		}
668 	    }
669 
670 	    if (t[0] == '$' && t[1] == '<') {
671 		for (t += 2;; t++) {
672 		    if (!(isdigit(UChar(*t))
673 			  || *t == '.'
674 			  || *t == '*'
675 			  || *t == '/'
676 			  || *t == '>')) {
677 			break;
678 		    }
679 		}
680 	    }
681 
682 	    /* we've now pushed s and t past any padding they pointed at */
683 
684 	    if (*s == '\0' && *t == '\0')
685 		return (0);
686 
687 	    if (*s != *t)
688 		return (*t - *s);
689 
690 	    /* else *s == *t but one is not NUL, so continue */
691 	    s++, t++;
692 	}
693     } else if (ok_s || ok_t) {
694 	return 1;
695     }
696     return 0;
697 }
698 
699 static void
700 append_acs0(string_desc * dst, int code, char *src, size_t off)
701 {
702     if (src != 0 && off < strlen(src)) {
703 	char temp[3];
704 	temp[0] = (char) code;
705 	temp[1] = src[off];
706 	temp[2] = 0;
707 	_nc_safe_strcat(dst, temp);
708     }
709 }
710 
711 static void
712 append_acs(string_desc * dst, int code, char *src)
713 {
714     if (VALID_STRING(src) && strlen(src) == 1) {
715 	append_acs0(dst, code, src, 0);
716     }
717 }
718 
719 /*
720  * The ko capability, if present, consists of a comma-separated capability
721  * list.  For each capability, we may assume there is a keycap that sends the
722  * string which is the value of that capability.
723  */
724 #define DATA(from, to) { { from }, { to } }
725 typedef struct {
726     const char from[3];
727     const char to[6];
728 } assoc;
729 static assoc const ko_xlate[] =
730 {
731     DATA("al", "kil1"),		/* insert line key  -> KEY_IL    */
732     DATA("bt", "kcbt"),		/* back tab         -> KEY_BTAB  */
733     DATA("cd", "ked"),		/* clear-to-eos key -> KEY_EOL   */
734     DATA("ce", "kel"),		/* clear-to-eol key -> KEY_EOS   */
735     DATA("cl", "kclr"),		/* clear key        -> KEY_CLEAR */
736     DATA("ct", "tbc"),		/* clear all tabs   -> KEY_CATAB */
737     DATA("dc", "kdch1"),	/* delete char      -> KEY_DC    */
738     DATA("dl", "kdl1"),		/* delete line      -> KEY_DL    */
739     DATA("do", "kcud1"),	/* down key         -> KEY_DOWN  */
740     DATA("ei", "krmir"),	/* exit insert key  -> KEY_EIC   */
741     DATA("ho", "khome"),	/* home key         -> KEY_HOME  */
742     DATA("ic", "kich1"),	/* insert char key  -> KEY_IC    */
743     DATA("im", "kIC"),		/* insert-mode key  -> KEY_SIC   */
744     DATA("le", "kcub1"),	/* le key           -> KEY_LEFT  */
745     DATA("nd", "kcuf1"),	/* nd key           -> KEY_RIGHT */
746     DATA("nl", "kent"),		/* new line key     -> KEY_ENTER */
747     DATA("st", "khts"),		/* set-tab key      -> KEY_STAB  */
748     DATA("ta", ""),
749     DATA("up", "kcuu1"),	/* up-arrow key     -> KEY_UP    */
750 };
751 
752 /*
753  * This routine fills in string caps that either had defaults under
754  * termcap or can be manufactured from obsolete termcap capabilities.
755  * It was lifted from Ross Ridge's mytinfo package.
756  */
757 
758 static const char C_CR[] = "\r";
759 static const char C_LF[] = "\n";
760 static const char C_BS[] = "\b";
761 static const char C_HT[] = "\t";
762 
763 /*
764  * This bit of legerdemain turns all the terminfo variable names into
765  * references to locations in the arrays Booleans, Numbers, and Strings ---
766  * precisely what's needed.
767  */
768 
769 #undef CUR
770 #define CUR tp->
771 
772 static void
773 postprocess_termcap(TERMTYPE2 *tp, bool has_base)
774 {
775     char buf[MAX_LINE * 2 + 2];
776     string_desc result;
777 
778     TR(TRACE_DATABASE,
779        (T_CALLED("postprocess_termcap(tp=%p, has_base=%d)"),
780 	(void *) tp, has_base));
781 
782     /*
783      * TERMCAP DEFAULTS AND OBSOLETE-CAPABILITY TRANSLATIONS
784      *
785      * This first part of the code is the functional inverse of the
786      * fragment in capdefaults.c.
787      * ----------------------------------------------------------------------
788      */
789 
790     /* if there was a tc entry, assume we picked up defaults via that */
791     if (!has_base) {
792 	if (WANTED(init_3string) && PRESENT(termcap_init2))
793 	    init_3string = _nc_save_str(termcap_init2);
794 
795 	if (WANTED(reset_2string) && PRESENT(termcap_reset))
796 	    reset_2string = _nc_save_str(termcap_reset);
797 
798 	if (WANTED(carriage_return)) {
799 	    if (carriage_return_delay > 0) {
800 		_nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
801 			    "%s$<%d>", C_CR, carriage_return_delay);
802 		carriage_return = _nc_save_str(buf);
803 	    } else
804 		carriage_return = _nc_save_str(C_CR);
805 	}
806 	if (WANTED(cursor_left)) {
807 	    if (backspace_delay > 0) {
808 		_nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
809 			    "%s$<%d>", C_BS, backspace_delay);
810 		cursor_left = _nc_save_str(buf);
811 	    } else if (backspaces_with_bs == 1)
812 		cursor_left = _nc_save_str(C_BS);
813 	    else if (PRESENT(backspace_if_not_bs))
814 		cursor_left = backspace_if_not_bs;
815 	}
816 	/* vi doesn't use "do", but it does seem to use nl (or '\n') instead */
817 	if (WANTED(cursor_down)) {
818 	    if (PRESENT(linefeed_if_not_lf))
819 		cursor_down = linefeed_if_not_lf;
820 	    else if (linefeed_is_newline != 1) {
821 		if (new_line_delay > 0) {
822 		    _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
823 				"%s$<%d>", C_LF, new_line_delay);
824 		    cursor_down = _nc_save_str(buf);
825 		} else
826 		    cursor_down = _nc_save_str(C_LF);
827 	    }
828 	}
829 	if (WANTED(scroll_forward) && crt_no_scrolling != 1) {
830 	    if (PRESENT(linefeed_if_not_lf))
831 		cursor_down = linefeed_if_not_lf;
832 	    else if (linefeed_is_newline != 1) {
833 		if (new_line_delay > 0) {
834 		    _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
835 				"%s$<%d>", C_LF, new_line_delay);
836 		    scroll_forward = _nc_save_str(buf);
837 		} else
838 		    scroll_forward = _nc_save_str(C_LF);
839 	    }
840 	}
841 	if (WANTED(newline)) {
842 	    if (linefeed_is_newline == 1) {
843 		if (new_line_delay > 0) {
844 		    _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
845 				"%s$<%d>", C_LF, new_line_delay);
846 		    newline = _nc_save_str(buf);
847 		} else
848 		    newline = _nc_save_str(C_LF);
849 	    } else if (PRESENT(carriage_return) && PRESENT(scroll_forward)) {
850 		_nc_str_init(&result, buf, sizeof(buf));
851 		if (_nc_safe_strcat(&result, carriage_return)
852 		    && _nc_safe_strcat(&result, scroll_forward))
853 		    newline = _nc_save_str(buf);
854 	    } else if (PRESENT(carriage_return) && PRESENT(cursor_down)) {
855 		_nc_str_init(&result, buf, sizeof(buf));
856 		if (_nc_safe_strcat(&result, carriage_return)
857 		    && _nc_safe_strcat(&result, cursor_down))
858 		    newline = _nc_save_str(buf);
859 	    }
860 	}
861     }
862 
863     /*
864      * Inverse of capdefaults.c code ends here.
865      * ----------------------------------------------------------------------
866      *
867      * TERMCAP-TO TERMINFO MAPPINGS FOR SOURCE TRANSLATION
868      *
869      * These translations will *not* be inverted by tgetent().
870      */
871 
872     if (!has_base) {
873 	/*
874 	 * We wait until now to decide if we've got a working cr because even
875 	 * one that doesn't work can be used for newline. Unfortunately the
876 	 * space allocated for it is wasted.
877 	 */
878 	if (return_does_clr_eol == 1 || no_correctly_working_cr == 1)
879 	    carriage_return = ABSENT_STRING;
880 
881 	/*
882 	 * Supposedly most termcap entries have ta now and '\t' is no longer a
883 	 * default, but it doesn't seem to be true...
884 	 */
885 	if (WANTED(tab)) {
886 	    if (horizontal_tab_delay > 0) {
887 		_nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
888 			    "%s$<%d>", C_HT, horizontal_tab_delay);
889 		tab = _nc_save_str(buf);
890 	    } else
891 		tab = _nc_save_str(C_HT);
892 	}
893 	if (init_tabs == ABSENT_NUMERIC && has_hardware_tabs == TRUE)
894 	    init_tabs = 8;
895 
896 	/*
897 	 * Assume we can beep with ^G unless we're given bl@.
898 	 */
899 	if (WANTED(bell))
900 	    bell = _nc_save_str("\007");
901     }
902 
903     /*
904      * Translate the old termcap :pt: capability to it#8 + ht=\t
905      */
906     if (has_hardware_tabs == TRUE) {
907 	if (init_tabs != 8 && init_tabs != ABSENT_NUMERIC)
908 	    _nc_warning("hardware tabs with a width other than 8: %d", init_tabs);
909 	else {
910 	    if (PRESENT(tab) && _nc_capcmp(tab, C_HT))
911 		_nc_warning("hardware tabs with a non-^I tab string %s",
912 			    _nc_visbuf(tab));
913 	    else {
914 		if (WANTED(tab))
915 		    tab = _nc_save_str(C_HT);
916 		init_tabs = 8;
917 	    }
918 	}
919     }
920     /*
921      * Now translate the ko capability, if there is one.  This
922      * isn't from mytinfo...
923      */
924     if (PRESENT(other_non_function_keys)) {
925 	char *base;
926 	char *bp, *cp, *dp;
927 	struct name_table_entry const *from_ptr;
928 	struct name_table_entry const *to_ptr;
929 	char buf2[MAX_TERMINFO_LENGTH];
930 	bool foundim;
931 
932 	/* we're going to use this for a special case later */
933 	dp = strchr(other_non_function_keys, 'i');
934 	foundim = (dp != 0) && (dp[1] == 'm');
935 
936 	/* look at each comma-separated capability in the ko string... */
937 	for (base = other_non_function_keys;
938 	     (cp = strchr(base, ',')) != 0;
939 	     base = cp + 1) {
940 	    size_t len = (unsigned) (cp - base);
941 	    size_t n;
942 	    assoc const *ap = 0;
943 
944 	    for (n = 0; n < SIZEOF(ko_xlate); ++n) {
945 		if (len == strlen(ko_xlate[n].from)
946 		    && strncmp(ko_xlate[n].from, base, len) == 0) {
947 		    ap = ko_xlate + n;
948 		    break;
949 		}
950 	    }
951 	    if (ap == 0) {
952 		_nc_warning("unknown capability `%.*s' in ko string",
953 			    (int) len, base);
954 		continue;
955 	    } else if (ap->to[0] == '\0')	/* ignore it */
956 		continue;
957 
958 	    /* now we know we found a match in ko_table, so... */
959 
960 	    from_ptr = _nc_find_entry(ap->from, _nc_get_hash_table(TRUE));
961 	    to_ptr = _nc_find_entry(ap->to, _nc_get_hash_table(FALSE));
962 
963 	    if (!from_ptr || !to_ptr)	/* should never happen! */
964 		_nc_err_abort("ko translation table is invalid, I give up");
965 
966 	    if (WANTED(tp->Strings[from_ptr->nte_index])) {
967 		_nc_warning("no value for ko capability %s", ap->from);
968 		continue;
969 	    }
970 
971 	    if (tp->Strings[to_ptr->nte_index]) {
972 		const char *s = tp->Strings[from_ptr->nte_index];
973 		const char *t = tp->Strings[to_ptr->nte_index];
974 		/* There's no point in warning about it if it is the same
975 		 * string; that's just an inefficiency.
976 		 */
977 		if (VALID_STRING(s) && VALID_STRING(t) && strcmp(s, t) != 0)
978 		    _nc_warning("%s (%s) already has an explicit value %s, ignoring ko",
979 				ap->to, ap->from, t);
980 		continue;
981 	    }
982 
983 	    /*
984 	     * The magic moment -- copy the mapped key string over,
985 	     * stripping out padding.
986 	     */
987 	    bp = tp->Strings[from_ptr->nte_index];
988 	    if (VALID_STRING(bp)) {
989 		for (dp = buf2; *bp; bp++) {
990 		    if (bp[0] == '$' && bp[1] == '<') {
991 			while (*bp && *bp != '>') {
992 			    ++bp;
993 			}
994 		    } else
995 			*dp++ = *bp;
996 		}
997 		*dp = '\0';
998 
999 		tp->Strings[to_ptr->nte_index] = _nc_save_str(buf2);
1000 	    } else {
1001 		tp->Strings[to_ptr->nte_index] = bp;
1002 	    }
1003 	}
1004 
1005 	/*
1006 	 * Note: ko=im and ko=ic both want to grab the `Insert'
1007 	 * keycap.  There's a kich1 but no ksmir, so the ic capability
1008 	 * got mapped to kich1 and im to kIC to avoid a collision.
1009 	 * If the description has im but not ic, hack kIC back to kich1.
1010 	 */
1011 	if (foundim && WANTED(key_ic) && PRESENT(key_sic)) {
1012 	    key_ic = key_sic;
1013 	    key_sic = ABSENT_STRING;
1014 	}
1015     }
1016 
1017     if (!has_base) {
1018 	if (!hard_copy) {
1019 	    if (WANTED(key_backspace))
1020 		key_backspace = _nc_save_str(C_BS);
1021 	    if (WANTED(key_left))
1022 		key_left = _nc_save_str(C_BS);
1023 	    if (WANTED(key_down))
1024 		key_down = _nc_save_str(C_LF);
1025 	}
1026     }
1027 
1028     /*
1029      * Translate XENIX forms characters.
1030      */
1031     if (PRESENT(acs_ulcorner) ||
1032 	PRESENT(acs_llcorner) ||
1033 	PRESENT(acs_urcorner) ||
1034 	PRESENT(acs_lrcorner) ||
1035 	PRESENT(acs_ltee) ||
1036 	PRESENT(acs_rtee) ||
1037 	PRESENT(acs_btee) ||
1038 	PRESENT(acs_ttee) ||
1039 	PRESENT(acs_hline) ||
1040 	PRESENT(acs_vline) ||
1041 	PRESENT(acs_plus)) {
1042 	char buf2[MAX_TERMCAP_LENGTH];
1043 
1044 	_nc_str_init(&result, buf2, sizeof(buf2));
1045 	_nc_safe_strcat(&result, acs_chars);
1046 
1047 	append_acs(&result, 'j', acs_lrcorner);
1048 	append_acs(&result, 'k', acs_urcorner);
1049 	append_acs(&result, 'l', acs_ulcorner);
1050 	append_acs(&result, 'm', acs_llcorner);
1051 	append_acs(&result, 'n', acs_plus);
1052 	append_acs(&result, 'q', acs_hline);
1053 	append_acs(&result, 't', acs_ltee);
1054 	append_acs(&result, 'u', acs_rtee);
1055 	append_acs(&result, 'v', acs_btee);
1056 	append_acs(&result, 'w', acs_ttee);
1057 	append_acs(&result, 'x', acs_vline);
1058 
1059 	if (buf2[0]) {
1060 	    acs_chars = _nc_save_str(buf2);
1061 	    _nc_warning("acsc string synthesized from XENIX capabilities");
1062 	}
1063     } else if (acs_chars == ABSENT_STRING
1064 	       && PRESENT(enter_alt_charset_mode)
1065 	       && PRESENT(exit_alt_charset_mode)) {
1066 	acs_chars = _nc_save_str(VT_ACSC);
1067     }
1068     returnVoidDB;
1069 }
1070 
1071 static void
1072 postprocess_terminfo(TERMTYPE2 *tp)
1073 {
1074     TR(TRACE_DATABASE,
1075        (T_CALLED("postprocess_terminfo(tp=%p)"),
1076 	(void *) tp));
1077 
1078     /*
1079      * TERMINFO-TO-TERMINFO MAPPINGS FOR SOURCE TRANSLATION
1080      * ----------------------------------------------------------------------
1081      */
1082 
1083     /*
1084      * Translate AIX forms characters.
1085      */
1086     if (PRESENT(box_chars_1)) {
1087 	char buf2[MAX_TERMCAP_LENGTH];
1088 	string_desc result;
1089 
1090 	_nc_str_init(&result, buf2, sizeof(buf2));
1091 	_nc_safe_strcat(&result, acs_chars);
1092 
1093 	append_acs0(&result, 'l', box_chars_1, 0);	/* ACS_ULCORNER */
1094 	append_acs0(&result, 'q', box_chars_1, 1);	/* ACS_HLINE */
1095 	append_acs0(&result, 'k', box_chars_1, 2);	/* ACS_URCORNER */
1096 	append_acs0(&result, 'x', box_chars_1, 3);	/* ACS_VLINE */
1097 	append_acs0(&result, 'j', box_chars_1, 4);	/* ACS_LRCORNER */
1098 	append_acs0(&result, 'm', box_chars_1, 5);	/* ACS_LLCORNER */
1099 	append_acs0(&result, 'w', box_chars_1, 6);	/* ACS_TTEE */
1100 	append_acs0(&result, 'u', box_chars_1, 7);	/* ACS_RTEE */
1101 	append_acs0(&result, 'v', box_chars_1, 8);	/* ACS_BTEE */
1102 	append_acs0(&result, 't', box_chars_1, 9);	/* ACS_LTEE */
1103 	append_acs0(&result, 'n', box_chars_1, 10);	/* ACS_PLUS */
1104 
1105 	if (buf2[0]) {
1106 	    acs_chars = _nc_save_str(buf2);
1107 	    _nc_warning("acsc string synthesized from AIX capabilities");
1108 	    box_chars_1 = ABSENT_STRING;
1109 	}
1110     }
1111     /*
1112      * ----------------------------------------------------------------------
1113      */
1114     returnVoidDB;
1115 }
1116 
1117 /*
1118  * Do a linear search through the terminfo tables to find a given full-name.
1119  * We don't expect to do this often, so there's no hashing function.
1120  *
1121  * In effect, this scans through the 3 lists of full-names, and looks them
1122  * up in _nc_info_table, which is organized so that the nte_index fields are
1123  * sorted, but the nte_type fields are not necessarily grouped together.
1124  */
1125 static struct name_table_entry const *
1126 lookup_fullname(const char *find)
1127 {
1128     int state = -1;
1129 
1130     for (;;) {
1131 	int count = 0;
1132 	NCURSES_CONST char *const *names;
1133 
1134 	switch (++state) {
1135 	case BOOLEAN:
1136 	    names = boolfnames;
1137 	    break;
1138 	case STRING:
1139 	    names = strfnames;
1140 	    break;
1141 	case NUMBER:
1142 	    names = numfnames;
1143 	    break;
1144 	default:
1145 	    return NOTFOUND;
1146 	}
1147 
1148 	for (count = 0; names[count] != 0; count++) {
1149 	    if (!strcmp(names[count], find)) {
1150 		struct name_table_entry const *entry_ptr = _nc_get_table(FALSE);
1151 		while (entry_ptr->nte_type != state
1152 		       || entry_ptr->nte_index != count)
1153 		    entry_ptr++;
1154 		return entry_ptr;
1155 	    }
1156 	}
1157     }
1158 }
1159 
1160 /* parse_entry.c ends here */
1161