]> ncurses.scripts.mit.edu Git - ncurses.git/blobdiff - ncurses/tinfo/parse_entry.c
ncurses 6.1 - patch 20190914
[ncurses.git] / ncurses / tinfo / parse_entry.c
index 578e86eec2401b588d53d6854b26718a298ded5c..f8cca8b5747824f6a551c0b4d92a46b08757bf7c 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * Copyright (c) 1998-2002,2003 Free Software Foundation, Inc.              *
+ * Copyright (c) 1998-2018,2019 Free Software Foundation, Inc.              *
  *                                                                          *
  * Permission is hereby granted, free of charge, to any person obtaining a  *
  * copy of this software and associated documentation files (the            *
@@ -29,7 +29,7 @@
 /****************************************************************************
  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
- *     and: Thomas E. Dickey                        1996-2003               *
+ *     and: Thomas E. Dickey                        1996-on                 *
  ****************************************************************************/
 
 /*
@@ -46,9 +46,8 @@
 
 #include <ctype.h>
 #include <tic.h>
-#include <term_entry.h>
 
-MODULE_ID("$Id: parse_entry.c,v 1.60 2003/11/08 21:57:09 tom Exp $")
+MODULE_ID("$Id: parse_entry.c,v 1.97 2019/08/03 23:10:38 tom Exp $")
 
 #ifdef LINT
 static short const parametrized[] =
@@ -57,17 +56,17 @@ static short const parametrized[] =
 #include <parametrized.h>
 #endif
 
-static void postprocess_termcap(TERMTYPE *, bool);
-static void postprocess_terminfo(TERMTYPE *);
+static void postprocess_termcap(TERMTYPE2 *, bool);
+static void postprocess_terminfo(TERMTYPE2 *);
 static struct name_table_entry const *lookup_fullname(const char *name);
 
 #if NCURSES_XNAMES
 
 static struct name_table_entry const *
-_nc_extend_names(ENTRY * entryp, char *name, int token_type)
+_nc_extend_names(ENTRY * entryp, const char *name, int token_type)
 {
     static struct name_table_entry temp;
-    TERMTYPE *tp = &(entryp->tterm);
+    TERMTYPE2 *tp = &(entryp->tterm);
     unsigned offset = 0;
     unsigned actual;
     unsigned tindex;
@@ -84,13 +83,13 @@ _nc_extend_names(ENTRY * entryp, char *name, int token_type)
     case NUMBER:
        first = tp->ext_Booleans;
        last = tp->ext_Numbers + first;
-       offset = tp->ext_Booleans + tp->ext_Numbers;
+       offset = (unsigned) (tp->ext_Booleans + tp->ext_Numbers);
        tindex = tp->num_Numbers;
        break;
     case STRING:
-       first = tp->ext_Booleans + tp->ext_Numbers;
+       first = (unsigned) (tp->ext_Booleans + tp->ext_Numbers);
        last = tp->ext_Strings + first;
-       offset = tp->ext_Booleans + tp->ext_Numbers + tp->ext_Strings;
+       offset = (unsigned) (tp->ext_Booleans + tp->ext_Numbers + tp->ext_Strings);
        tindex = tp->num_Strings;
        break;
     case CANCEL:
@@ -137,32 +136,36 @@ _nc_extend_names(ENTRY * entryp, char *name, int token_type)
            break;
        }
     }
+
+#define for_each_value(max) \
+       for (last = (unsigned) (max - 1); last > tindex; last--)
+
     if (!found) {
        switch (token_type) {
        case BOOLEAN:
-           tp->ext_Booleans += 1;
-           tp->num_Booleans += 1;
-           tp->Booleans = typeRealloc(char, tp->num_Booleans, tp->Booleans);
-           for (last = tp->num_Booleans - 1; last > tindex; last--)
+           tp->ext_Booleans++;
+           tp->num_Booleans++;
+           TYPE_REALLOC(NCURSES_SBOOL, tp->num_Booleans, tp->Booleans);
+           for_each_value(tp->num_Booleans)
                tp->Booleans[last] = tp->Booleans[last - 1];
            break;
        case NUMBER:
-           tp->ext_Numbers += 1;
-           tp->num_Numbers += 1;
-           tp->Numbers = typeRealloc(short, tp->num_Numbers, tp->Numbers);
-           for (last = tp->num_Numbers - 1; last > tindex; last--)
+           tp->ext_Numbers++;
+           tp->num_Numbers++;
+           TYPE_REALLOC(NCURSES_INT2, tp->num_Numbers, tp->Numbers);
+           for_each_value(tp->num_Numbers)
                tp->Numbers[last] = tp->Numbers[last - 1];
            break;
        case STRING:
-           tp->ext_Strings += 1;
-           tp->num_Strings += 1;
-           tp->Strings = typeRealloc(char *, tp->num_Strings, tp->Strings);
-           for (last = tp->num_Strings - 1; last > tindex; last--)
+           tp->ext_Strings++;
+           tp->num_Strings++;
+           TYPE_REALLOC(char *, tp->num_Strings, tp->Strings);
+           for_each_value(tp->num_Strings)
                tp->Strings[last] = tp->Strings[last - 1];
            break;
        }
        actual = NUM_EXT_NAMES(tp);
-       tp->ext_Names = typeRealloc(char *, actual, tp->ext_Names);
+       TYPE_REALLOC(char *, actual, tp->ext_Names);
        while (--actual > offset)
            tp->ext_Names[actual] = tp->ext_Names[actual - 1];
        tp->ext_Names[offset] = _nc_save_str(name);
@@ -170,13 +173,60 @@ _nc_extend_names(ENTRY * entryp, char *name, int token_type)
 
     temp.nte_name = tp->ext_Names[offset];
     temp.nte_type = token_type;
-    temp.nte_index = tindex;
+    temp.nte_index = (short) tindex;
     temp.nte_link = -1;
 
     return &temp;
 }
+
+static const char *
+usertype2s(int mask)
+{
+    const char *result = "unknown";
+    if (mask & (1 << BOOLEAN)) {
+       result = "boolean";
+    } else if (mask & (1 << NUMBER)) {
+       result = "number";
+    } else if (mask & (1 << STRING)) {
+       result = "string";
+    }
+    return result;
+}
+
+static bool
+expected_type(const char *name, int token_type, bool silent)
+{
+    struct user_table_entry const *entry = _nc_find_user_entry(name);
+    bool result = TRUE;
+    if ((entry != 0) && (token_type != CANCEL)) {
+       int have_type = (1 << token_type);
+       if (!(entry->ute_type & have_type)) {
+           if (!silent)
+               _nc_warning("expected %s-type for %s, have %s",
+                           usertype2s(entry->ute_type),
+                           name,
+                           usertype2s(have_type));
+           result = FALSE;
+       }
+    }
+    return result;
+}
 #endif /* NCURSES_XNAMES */
 
+static bool
+valid_entryname(const char *name)
+{
+    bool result = TRUE;
+    int ch;
+    while ((ch = UChar(*name++)) != '\0') {
+       if (ch <= ' ' || ch > '~' || ch == '/') {
+           result = FALSE;
+           break;
+       }
+    }
+    return result;
+}
+
 /*
  *     int
  *     _nc_parse_entry(entry, literal, silent)
@@ -200,12 +250,15 @@ _nc_extend_names(ENTRY * entryp, char *name, int token_type)
        { bad_tc_usage = TRUE; \
         _nc_warning("Legacy termcap allows only a trailing tc= clause"); }
 
+#define MAX_NUMBER MAX_OF_TYPE(NCURSES_INT2)
+
 NCURSES_EXPORT(int)
-_nc_parse_entry(struct entry *entryp, int literal, bool silent)
+_nc_parse_entry(ENTRY * entryp, int literal, bool silent)
 {
     int token_type;
     struct name_table_entry const *entry_ptr;
     char *ptr, *base;
+    const char *name;
     bool bad_tc_usage = FALSE;
 
     token_type = _nc_get_token(silent);
@@ -215,7 +268,7 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
     if (token_type != NAMES)
        _nc_err_abort("Entry does not start with terminal names in column one");
 
-    _nc_init_entry(&entryp->tterm);
+    _nc_init_entry(entryp);
 
     entryp->cstart = _nc_comment_start;
     entryp->cend = _nc_comment_end;
@@ -231,9 +284,14 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
      * implemented it.  Note that the resulting terminal type was never the
      * 2-character name, but was instead the first alias after that.
      */
+#define ok_TC2(s) (isgraph(UChar(s)) && (s) != '|')
     ptr = _nc_curr_token.tk_name;
-    if (_nc_syntax == SYN_TERMCAP) {
-       if (ptr[2] == '|') {
+    if (_nc_syntax == SYN_TERMCAP
+#if NCURSES_XNAMES
+       && !_nc_user_definable
+#endif
+       ) {
+       if (ok_TC2(ptr[0]) && ok_TC2(ptr[1]) && (ptr[2] == '|')) {
            ptr += 3;
            _nc_curr_token.tk_name[2] = '\0';
        }
@@ -241,6 +299,9 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
 
     entryp->tterm.str_table = entryp->tterm.term_names = _nc_save_str(ptr);
 
+    if (entryp->tterm.str_table == 0)
+       return (ERR);
+
     DEBUG(1, ("Starting '%s'", ptr));
 
     /*
@@ -248,7 +309,12 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
      * results in the terminal type getting prematurely set to correspond
      * to that of the next entry.
      */
-    _nc_set_type(_nc_first_name(entryp->tterm.term_names));
+    name = _nc_first_name(entryp->tterm.term_names);
+    if (!valid_entryname(name)) {
+       _nc_warning("invalid entry name \"%s\"", name);
+       name = "invalid";
+    }
+    _nc_set_type(name);
 
     /* check for overly-long names and aliases */
     for (base = entryp->tterm.term_names; (ptr = strchr(base, '|')) != 0;
@@ -270,6 +336,19 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
        bool is_use = (strcmp(_nc_curr_token.tk_name, "use") == 0);
        bool is_tc = !is_use && (strcmp(_nc_curr_token.tk_name, "tc") == 0);
        if (is_use || is_tc) {
+           if (!VALID_STRING(_nc_curr_token.tk_valstring)
+               || _nc_curr_token.tk_valstring[0] == '\0') {
+               _nc_warning("missing name for use-clause");
+               continue;
+           } else if (!valid_entryname(_nc_curr_token.tk_valstring)) {
+               _nc_warning("invalid name for use-clause \"%s\"",
+                           _nc_curr_token.tk_valstring);
+               continue;
+           } else if (entryp->nuses >= MAX_USES) {
+               _nc_warning("too many use-clauses, ignored \"%s\"",
+                           _nc_curr_token.tk_valstring);
+               continue;
+           }
            entryp->uses[entryp->nuses].name = _nc_save_str(_nc_curr_token.tk_valstring);
            entryp->uses[entryp->nuses].line = _nc_curr_line;
            entryp->nuses++;
@@ -279,7 +358,7 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
        } else {
            /* normal token lookup */
            entry_ptr = _nc_find_entry(_nc_curr_token.tk_name,
-                                      _nc_syntax ? _nc_cap_hash_table : _nc_info_hash_table);
+                                      _nc_get_hash_table(_nc_syntax));
 
            /*
             * Our kluge to handle aliasing.  The reason it's done
@@ -296,7 +375,7 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
                    if (entryp->nuses != 0) {
                        BAD_TC_USAGE
                    }
-                   for (ap = _nc_capalias_table; ap->from; ap++)
+                   for (ap = _nc_get_alias_table(TRUE); ap->from; ap++)
                        if (strcmp(ap->from, _nc_curr_token.tk_name) == 0) {
                            if (ap->to == (char *) 0) {
                                _nc_warning("%s (%s termcap extension) ignored",
@@ -304,14 +383,15 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
                                goto nexttok;
                            }
 
-                           entry_ptr = _nc_find_entry(ap->to, _nc_cap_hash_table);
+                           entry_ptr = _nc_find_entry(ap->to,
+                                                      _nc_get_hash_table(TRUE));
                            if (entry_ptr && !silent)
                                _nc_warning("%s (%s termcap extension) aliased to %s",
                                            ap->from, ap->source, ap->to);
                            break;
                        }
                } else {        /* if (_nc_syntax == SYN_TERMINFO) */
-                   for (ap = _nc_infoalias_table; ap->from; ap++)
+                   for (ap = _nc_get_alias_table(FALSE); ap->from; ap++)
                        if (strcmp(ap->from, _nc_curr_token.tk_name) == 0) {
                            if (ap->to == (char *) 0) {
                                _nc_warning("%s (%s terminfo extension) ignored",
@@ -319,7 +399,8 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
                                goto nexttok;
                            }
 
-                           entry_ptr = _nc_find_entry(ap->to, _nc_info_hash_table);
+                           entry_ptr = _nc_find_entry(ap->to,
+                                                      _nc_get_hash_table(FALSE));
                            if (entry_ptr && !silent)
                                _nc_warning("%s (%s terminfo extension) aliased to %s",
                                            ap->from, ap->source, ap->to);
@@ -337,12 +418,20 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
             * define a name based on its context.
             */
            if (entry_ptr == NOTFOUND
-               && _nc_user_definable
-               && (entry_ptr = _nc_extend_names(entryp,
-                                                _nc_curr_token.tk_name,
-                                                token_type)) != 0) {
-               if (_nc_tracing >= DEBUG_LEVEL(1))
-                   _nc_warning("extended capability '%s'", _nc_curr_token.tk_name);
+               && _nc_user_definable) {
+               if (expected_type(_nc_curr_token.tk_name, token_type, silent)) {
+                   if ((entry_ptr = _nc_extend_names(entryp,
+                                                     _nc_curr_token.tk_name,
+                                                     token_type)) != 0) {
+                       if (_nc_tracing >= DEBUG_LEVEL(1)) {
+                           _nc_warning("extended capability '%s'",
+                                       _nc_curr_token.tk_name);
+                       }
+                   }
+               } else {
+                   /* ignore it: we have already printed error message */
+                   continue;
+               }
            }
 #endif /* NCURSES_XNAMES */
 
@@ -355,7 +444,16 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
            }
 
            /* deal with bad type/value combinations. */
-           if (token_type != CANCEL && entry_ptr->nte_type != token_type) {
+           if (token_type == CANCEL) {
+               /*
+                * Prefer terminfo in this (long-obsolete) ambiguity:
+                */
+               if (!strcmp("ma", _nc_curr_token.tk_name)) {
+                   entry_ptr = _nc_find_type_entry("ma", NUMBER,
+                                                   _nc_syntax != 0);
+                   assert(entry_ptr != 0);
+               }
+           } else if (entry_ptr->nte_type != token_type) {
                /*
                 * Nasty special cases here handle situations in which type
                 * information can resolve name clashes.  Normal lookup
@@ -366,23 +464,26 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
                 * type, this will do the job.
                 */
 
-               /* tell max_attributes from arrow_key_map */
-               if (token_type == NUMBER && !strcmp("ma", _nc_curr_token.tk_name))
+               if (token_type == NUMBER
+                   && !strcmp("ma", _nc_curr_token.tk_name)) {
+                   /* tell max_attributes from arrow_key_map */
                    entry_ptr = _nc_find_type_entry("ma", NUMBER,
-                                                   _nc_get_table(_nc_syntax
-                                                                 != 0));
+                                                   _nc_syntax != 0);
+                   assert(entry_ptr != 0);
 
-               /* map terminfo's string MT to MT */
-               else if (token_type == STRING && !strcmp("MT", _nc_curr_token.tk_name))
+               } else if (token_type == STRING
+                          && !strcmp("MT", _nc_curr_token.tk_name)) {
+                   /* map terminfo's string MT to MT */
                    entry_ptr = _nc_find_type_entry("MT", STRING,
-                                                   _nc_get_table(_nc_syntax
-                                                                 != 0));
+                                                   _nc_syntax != 0);
+                   assert(entry_ptr != 0);
 
-               /* treat strings without following "=" as empty strings */
-               else if (token_type == BOOLEAN && entry_ptr->nte_type == STRING)
+               } else if (token_type == BOOLEAN
+                          && entry_ptr->nte_type == STRING) {
+                   /* treat strings without following "=" as empty strings */
                    token_type = STRING;
-               /* we couldn't recover; skip this token */
-               else {
+               } else {
+                   /* we couldn't recover; skip this token */
                    if (!silent) {
                        const char *type_name;
                        switch (entry_ptr->nte_type) {
@@ -429,8 +530,15 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
                break;
 
            case NUMBER:
-               entryp->tterm.Numbers[entry_ptr->nte_index] =
-                   _nc_curr_token.tk_valnumber;
+#if !NCURSES_EXT_NUMBERS
+               if (_nc_curr_token.tk_valnumber > MAX_NUMBER) {
+                   entryp->tterm.Numbers[entry_ptr->nte_index] = MAX_NUMBER;
+               } else
+#endif
+               {
+                   entryp->tterm.Numbers[entry_ptr->nte_index] =
+                       (NCURSES_INT2) _nc_curr_token.tk_valnumber;
+               }
                break;
 
            case STRING:
@@ -445,7 +553,7 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
            default:
                if (!silent)
                    _nc_warning("unknown token type");
-               _nc_panic_mode((_nc_syntax == SYN_TERMCAP) ? ':' : ',');
+               _nc_panic_mode((char) ((_nc_syntax == SYN_TERMCAP) ? ':' : ','));
                continue;
            }
        }                       /* end else cur_token.name != "use" */
@@ -465,23 +573,26 @@ _nc_parse_entry(struct entry *entryp, int literal, bool silent)
     if (!literal) {
        if (_nc_syntax == SYN_TERMCAP) {
            bool has_base_entry = FALSE;
-           int i;
 
            /*
             * Don't insert defaults if this is a `+' entry meant only
             * for inclusion in other entries (not sure termcap ever
             * had these, actually).
             */
-           if (strchr(entryp->tterm.term_names, '+'))
+           if (strchr(entryp->tterm.term_names, '+')) {
                has_base_entry = TRUE;
-           else
+           } else {
+               unsigned i;
                /*
                 * Otherwise, look for a base entry that will already
                 * have picked up defaults via translation.
                 */
-               for (i = 0; i < entryp->nuses; i++)
-                   if (!strchr((char *) entryp->uses[i].name, '+'))
+               for (i = 0; i < entryp->nuses; i++) {
+                   if (entryp->uses[i].name != 0
+                       && !strchr(entryp->uses[i].name, '+'))
                        has_base_entry = TRUE;
+               }
+           }
 
            postprocess_termcap(&entryp->tterm, has_base_entry);
        } else
@@ -496,43 +607,50 @@ NCURSES_EXPORT(int)
 _nc_capcmp(const char *s, const char *t)
 /* compare two string capabilities, stripping out padding */
 {
-    if (!s && !t)
-       return (0);
-    else if (!s || !t)
-       return (1);
-
-    for (;;) {
-       if (s[0] == '$' && s[1] == '<') {
-           for (s += 2;; s++)
-               if (!(isdigit(UChar(*s))
-                     || *s == '.'
-                     || *s == '*'
-                     || *s == '/'
-                     || *s == '>'))
-                   break;
-       }
+    bool ok_s = VALID_STRING(s);
+    bool ok_t = VALID_STRING(t);
+
+    if (ok_s && ok_t) {
+       for (;;) {
+           if (s[0] == '$' && s[1] == '<') {
+               for (s += 2;; s++) {
+                   if (!(isdigit(UChar(*s))
+                         || *s == '.'
+                         || *s == '*'
+                         || *s == '/'
+                         || *s == '>')) {
+                       break;
+                   }
+               }
+           }
 
-       if (t[0] == '$' && t[1] == '<') {
-           for (t += 2;; t++)
-               if (!(isdigit(UChar(*t))
-                     || *t == '.'
-                     || *t == '*'
-                     || *t == '/'
-                     || *t == '>'))
-                   break;
-       }
+           if (t[0] == '$' && t[1] == '<') {
+               for (t += 2;; t++) {
+                   if (!(isdigit(UChar(*t))
+                         || *t == '.'
+                         || *t == '*'
+                         || *t == '/'
+                         || *t == '>')) {
+                       break;
+                   }
+               }
+           }
 
-       /* we've now pushed s and t past any padding they were pointing at */
+           /* we've now pushed s and t past any padding they pointed at */
 
-       if (*s == '\0' && *t == '\0')
-           return (0);
+           if (*s == '\0' && *t == '\0')
+               return (0);
 
-       if (*s != *t)
-           return (*t - *s);
+           if (*s != *t)
+               return (*t - *s);
 
-       /* else *s == *t but one is not NUL, so continue */
-       s++, t++;
+           /* else *s == *t but one is not NUL, so continue */
+           s++, t++;
+       }
+    } else if (ok_s || ok_t) {
+       return 1;
     }
+    return 0;
 }
 
 static void
@@ -540,8 +658,8 @@ append_acs0(string_desc * dst, int code, int src)
 {
     if (src != 0) {
        char temp[3];
-       temp[0] = code;
-       temp[1] = src;
+       temp[0] = (char) code;
+       temp[1] = (char) src;
        temp[2] = 0;
        _nc_safe_strcat(dst, temp);
     }
@@ -550,7 +668,7 @@ append_acs0(string_desc * dst, int code, int src)
 static void
 append_acs(string_desc * dst, int code, char *src)
 {
-    if (src != 0 && strlen(src) == 1) {
+    if (VALID_STRING(src) && strlen(src) == 1) {
        append_acs0(dst, code, *src);
     }
 }
@@ -560,32 +678,32 @@ append_acs(string_desc * dst, int code, char *src)
  * list.  For each capability, we may assume there is a keycap that sends the
  * string which is the value of that capability.
  */
+#define DATA(from, to) { { from }, { to } }
 typedef struct {
-    const char *from;
-    const char *to;
+    const char from[3];
+    const char to[6];
 } assoc;
 static assoc const ko_xlate[] =
 {
-    {"al", "kil1"},            /* insert line key  -> KEY_IL    */
-    {"bt", "kcbt"},            /* back tab         -> KEY_BTAB  */
-    {"cd", "ked"},             /* clear-to-eos key -> KEY_EOL   */
-    {"ce", "kel"},             /* clear-to-eol key -> KEY_EOS   */
-    {"cl", "kclr"},            /* clear key        -> KEY_CLEAR */
-    {"ct", "tbc"},             /* clear all tabs   -> KEY_CATAB */
-    {"dc", "kdch1"},           /* delete char      -> KEY_DC    */
-    {"dl", "kdl1"},            /* delete line      -> KEY_DL    */
-    {"do", "kcud1"},           /* down key         -> KEY_DOWN  */
-    {"ei", "krmir"},           /* exit insert key  -> KEY_EIC   */
-    {"ho", "khome"},           /* home key         -> KEY_HOME  */
-    {"ic", "kich1"},           /* insert char key  -> KEY_IC    */
-    {"im", "kIC"},             /* insert-mode key  -> KEY_SIC   */
-    {"le", "kcub1"},           /* le key           -> KEY_LEFT  */
-    {"nd", "kcuf1"},           /* nd key           -> KEY_RIGHT */
-    {"nl", "kent"},            /* new line key     -> KEY_ENTER */
-    {"st", "khts"},            /* set-tab key      -> KEY_STAB  */
-    {"ta", CANCELLED_STRING},
-    {"up", "kcuu1"},           /* up-arrow key     -> KEY_UP    */
-    {(char *) 0, (char *) 0},
+    DATA("al", "kil1"),                /* insert line key  -> KEY_IL    */
+    DATA("bt", "kcbt"),                /* back tab         -> KEY_BTAB  */
+    DATA("cd", "ked"),         /* clear-to-eos key -> KEY_EOL   */
+    DATA("ce", "kel"),         /* clear-to-eol key -> KEY_EOS   */
+    DATA("cl", "kclr"),                /* clear key        -> KEY_CLEAR */
+    DATA("ct", "tbc"),         /* clear all tabs   -> KEY_CATAB */
+    DATA("dc", "kdch1"),       /* delete char      -> KEY_DC    */
+    DATA("dl", "kdl1"),                /* delete line      -> KEY_DL    */
+    DATA("do", "kcud1"),       /* down key         -> KEY_DOWN  */
+    DATA("ei", "krmir"),       /* exit insert key  -> KEY_EIC   */
+    DATA("ho", "khome"),       /* home key         -> KEY_HOME  */
+    DATA("ic", "kich1"),       /* insert char key  -> KEY_IC    */
+    DATA("im", "kIC"),         /* insert-mode key  -> KEY_SIC   */
+    DATA("le", "kcub1"),       /* le key           -> KEY_LEFT  */
+    DATA("nd", "kcuf1"),       /* nd key           -> KEY_RIGHT */
+    DATA("nl", "kent"),                /* new line key     -> KEY_ENTER */
+    DATA("st", "khts"),                /* set-tab key      -> KEY_STAB  */
+    DATA("ta", ""),
+    DATA("up", "kcuu1"),       /* up-arrow key     -> KEY_UP    */
 };
 
 /*
@@ -599,13 +717,6 @@ static const char C_LF[] = "\n";
 static const char C_BS[] = "\b";
 static const char C_HT[] = "\t";
 
-/*
- * Note that WANTED and PRESENT are not simple inverses!  If a capability
- * has been explicitly cancelled, it's not considered WANTED.
- */
-#define WANTED(s)      ((s) == ABSENT_STRING)
-#define PRESENT(s)     (((s) != ABSENT_STRING) && ((s) != CANCELLED_STRING))
-
 /*
  * This bit of legerdemain turns all the terminfo variable names into
  * references to locations in the arrays Booleans, Numbers, and Strings ---
@@ -616,7 +727,7 @@ static const char C_HT[] = "\t";
 #define CUR tp->
 
 static void
-postprocess_termcap(TERMTYPE * tp, bool has_base)
+postprocess_termcap(TERMTYPE2 *tp, bool has_base)
 {
     char buf[MAX_LINE * 2 + 2];
     string_desc result;
@@ -631,35 +742,38 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
 
     /* if there was a tc entry, assume we picked up defaults via that */
     if (!has_base) {
-       if (WANTED(init_3string) && termcap_init2)
+       if (WANTED(init_3string) && PRESENT(termcap_init2))
            init_3string = _nc_save_str(termcap_init2);
 
-       if (WANTED(reset_2string) && termcap_reset)
+       if (WANTED(reset_2string) && PRESENT(termcap_reset))
            reset_2string = _nc_save_str(termcap_reset);
 
        if (WANTED(carriage_return)) {
            if (carriage_return_delay > 0) {
-               sprintf(buf, "%s$<%d>", C_CR, carriage_return_delay);
+               _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                           "%s$<%d>", C_CR, carriage_return_delay);
                carriage_return = _nc_save_str(buf);
            } else
                carriage_return = _nc_save_str(C_CR);
        }
        if (WANTED(cursor_left)) {
            if (backspace_delay > 0) {
-               sprintf(buf, "%s$<%d>", C_BS, backspace_delay);
+               _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                           "%s$<%d>", C_BS, backspace_delay);
                cursor_left = _nc_save_str(buf);
            } else if (backspaces_with_bs == 1)
                cursor_left = _nc_save_str(C_BS);
            else if (PRESENT(backspace_if_not_bs))
                cursor_left = backspace_if_not_bs;
        }
-       /* vi doesn't use "do", but it does seems to use nl (or '\n') instead */
+       /* vi doesn't use "do", but it does seem to use nl (or '\n') instead */
        if (WANTED(cursor_down)) {
            if (PRESENT(linefeed_if_not_lf))
                cursor_down = linefeed_if_not_lf;
            else if (linefeed_is_newline != 1) {
                if (new_line_delay > 0) {
-                   sprintf(buf, "%s$<%d>", C_LF, new_line_delay);
+                   _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                               "%s$<%d>", C_LF, new_line_delay);
                    cursor_down = _nc_save_str(buf);
                } else
                    cursor_down = _nc_save_str(C_LF);
@@ -670,7 +784,8 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
                cursor_down = linefeed_if_not_lf;
            else if (linefeed_is_newline != 1) {
                if (new_line_delay > 0) {
-                   sprintf(buf, "%s$<%d>", C_LF, new_line_delay);
+                   _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                               "%s$<%d>", C_LF, new_line_delay);
                    scroll_forward = _nc_save_str(buf);
                } else
                    scroll_forward = _nc_save_str(C_LF);
@@ -679,7 +794,8 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
        if (WANTED(newline)) {
            if (linefeed_is_newline == 1) {
                if (new_line_delay > 0) {
-                   sprintf(buf, "%s$<%d>", C_LF, new_line_delay);
+                   _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                               "%s$<%d>", C_LF, new_line_delay);
                    newline = _nc_save_str(buf);
                } else
                    newline = _nc_save_str(C_LF);
@@ -721,7 +837,8 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
         */
        if (WANTED(tab)) {
            if (horizontal_tab_delay > 0) {
-               sprintf(buf, "%s$<%d>", C_HT, horizontal_tab_delay);
+               _nc_SPRINTF(buf, _nc_SLIMIT(sizeof(buf))
+                           "%s$<%d>", C_HT, horizontal_tab_delay);
                tab = _nc_save_str(buf);
            } else
                tab = _nc_save_str(C_HT);
@@ -743,7 +860,7 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
        if (init_tabs != 8 && init_tabs != ABSENT_NUMERIC)
            _nc_warning("hardware tabs with a width other than 8: %d", init_tabs);
        else {
-           if (tab && _nc_capcmp(tab, C_HT))
+           if (PRESENT(tab) && _nc_capcmp(tab, C_HT))
                _nc_warning("hardware tabs with a non-^I tab string %s",
                            _nc_visbuf(tab));
            else {
@@ -758,11 +875,10 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
      * isn't from mytinfo...
      */
     if (PRESENT(other_non_function_keys)) {
-       char *base = other_non_function_keys;
+       char *base;
        char *bp, *cp, *dp;
        struct name_table_entry const *from_ptr;
        struct name_table_entry const *to_ptr;
-       assoc const *ap;
        char buf2[MAX_TERMINFO_LENGTH];
        bool foundim;
 
@@ -774,23 +890,28 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
        for (base = other_non_function_keys;
             (cp = strchr(base, ',')) != 0;
             base = cp + 1) {
-           size_t len = cp - base;
-
-           for (ap = ko_xlate; ap->from; ap++)
-               if (len == strlen(ap->from)
-                   && strncmp(ap->from, base, len) == 0)
+           size_t len = (unsigned) (cp - base);
+           size_t n;
+           assoc const *ap = 0;
+
+           for (n = 0; n < SIZEOF(ko_xlate); ++n) {
+               if (len == strlen(ko_xlate[n].from)
+                   && strncmp(ko_xlate[n].from, base, len) == 0) {
+                   ap = ko_xlate + n;
                    break;
-           if (!ap->to) {
+               }
+           }
+           if (ap == 0) {
                _nc_warning("unknown capability `%.*s' in ko string",
                            (int) len, base);
                continue;
-           } else if (ap->to == CANCELLED_STRING)      /* ignore it */
+           } else if (ap->to[0] == '\0')       /* ignore it */
                continue;
 
            /* now we know we found a match in ko_table, so... */
 
-           from_ptr = _nc_find_entry(ap->from, _nc_cap_hash_table);
-           to_ptr = _nc_find_entry(ap->to, _nc_info_hash_table);
+           from_ptr = _nc_find_entry(ap->from, _nc_get_hash_table(TRUE));
+           to_ptr = _nc_find_entry(ap->to, _nc_get_hash_table(FALSE));
 
            if (!from_ptr || !to_ptr)   /* should never happen! */
                _nc_err_abort("ko translation table is invalid, I give up");
@@ -801,15 +922,14 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
            }
 
            if (tp->Strings[to_ptr->nte_index]) {
+               const char *s = tp->Strings[from_ptr->nte_index];
+               const char *t = tp->Strings[to_ptr->nte_index];
                /* There's no point in warning about it if it's the same
                 * string; that's just an inefficiency.
                 */
-               if (strcmp(
-                             tp->Strings[from_ptr->nte_index],
-                             tp->Strings[to_ptr->nte_index]) != 0)
+               if (VALID_STRING(s) && VALID_STRING(t) && strcmp(s, t) != 0)
                    _nc_warning("%s (%s) already has an explicit value %s, ignoring ko",
-                               ap->to, ap->from,
-                               _nc_visbuf(tp->Strings[to_ptr->nte_index]));
+                               ap->to, ap->from, t);
                continue;
            }
 
@@ -817,17 +937,22 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
             * The magic moment -- copy the mapped key string over,
             * stripping out padding.
             */
-           for (dp = buf2, bp = tp->Strings[from_ptr->nte_index]; *bp; bp++) {
-               if (bp[0] == '$' && bp[1] == '<') {
-                   while (*bp && *bp != '>') {
-                       ++bp;
-                   }
-               } else
-                   *dp++ = *bp;
-           }
-           *dp++ = '\0';
+           bp = tp->Strings[from_ptr->nte_index];
+           if (VALID_STRING(bp)) {
+               for (dp = buf2; *bp; bp++) {
+                   if (bp[0] == '$' && bp[1] == '<') {
+                       while (*bp && *bp != '>') {
+                           ++bp;
+                       }
+                   } else
+                       *dp++ = *bp;
+               }
+               *dp = '\0';
 
-           tp->Strings[to_ptr->nte_index] = _nc_save_str(buf2);
+               tp->Strings[to_ptr->nte_index] = _nc_save_str(buf2);
+           } else {
+               tp->Strings[to_ptr->nte_index] = bp;
+           }
        }
 
        /*
@@ -836,7 +961,7 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
         * got mapped to kich1 and im to kIC to avoid a collision.
         * If the description has im but not ic, hack kIC back to kich1.
         */
-       if (foundim && WANTED(key_ic) && key_sic) {
+       if (foundim && WANTED(key_ic) && PRESENT(key_sic)) {
            key_ic = key_sic;
            key_sic = ABSENT_STRING;
        }
@@ -888,15 +1013,15 @@ postprocess_termcap(TERMTYPE * tp, bool has_base)
            acs_chars = _nc_save_str(buf2);
            _nc_warning("acsc string synthesized from XENIX capabilities");
        }
-    } else if (acs_chars == 0
-              && enter_alt_charset_mode != 0
-              && exit_alt_charset_mode != 0) {
+    } else if (acs_chars == ABSENT_STRING
+              && PRESENT(enter_alt_charset_mode)
+              && PRESENT(exit_alt_charset_mode)) {
        acs_chars = _nc_save_str(VT_ACSC);
     }
 }
 
 static void
-postprocess_terminfo(TERMTYPE * tp)
+postprocess_terminfo(TERMTYPE2 *tp)
 {
     /*
      * TERMINFO-TO-TERMINFO MAPPINGS FOR SOURCE TRANSLATION