1 /* 2 * Copyright (C) 1984-2022 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 #include <stdio.h> 11 #include <string.h> 12 #include <stdlib.h> 13 #include "lesskey.h" 14 #include "cmd.h" 15 #include "xbuf.h" 16 #include "defines.h" 17 18 #define CONTROL(c) ((c)&037) 19 #define ESC CONTROL('[') 20 21 extern void lesskey_parse_error(char *msg); 22 extern char *homefile(char *filename); 23 extern void *ecalloc(int count, unsigned int size); 24 extern int lstrtoi(char *str, char **end); 25 extern char version[]; 26 27 static int linenum; 28 static int errors; 29 static int less_version = 0; 30 static char *lesskey_file; 31 32 static struct lesskey_cmdname cmdnames[] = 33 { 34 { "back-bracket", A_B_BRACKET }, 35 { "back-line", A_B_LINE }, 36 { "back-line-force", A_BF_LINE }, 37 { "back-screen", A_B_SCREEN }, 38 { "back-scroll", A_B_SCROLL }, 39 { "back-search", A_B_SEARCH }, 40 { "back-window", A_B_WINDOW }, 41 { "clear-mark", A_CLRMARK }, 42 { "debug", A_DEBUG }, 43 { "digit", A_DIGIT }, 44 { "display-flag", A_DISP_OPTION }, 45 { "display-option", A_DISP_OPTION }, 46 { "end", A_GOEND }, 47 { "end-scroll", A_RRSHIFT }, 48 { "examine", A_EXAMINE }, 49 { "filter", A_FILTER }, 50 { "first-cmd", A_FIRSTCMD }, 51 { "firstcmd", A_FIRSTCMD }, 52 { "flush-repaint", A_FREPAINT }, 53 { "forw-bracket", A_F_BRACKET }, 54 { "forw-forever", A_F_FOREVER }, 55 { "forw-until-hilite", A_F_UNTIL_HILITE }, 56 { "forw-line", A_F_LINE }, 57 { "forw-line-force", A_FF_LINE }, 58 { "forw-screen", A_F_SCREEN }, 59 { "forw-screen-force", A_FF_SCREEN }, 60 { "forw-scroll", A_F_SCROLL }, 61 { "forw-search", A_F_SEARCH }, 62 { "forw-window", A_F_WINDOW }, 63 { "goto-end", A_GOEND }, 64 { "goto-end-buffered", A_GOEND_BUF }, 65 { "goto-line", A_GOLINE }, 66 { "goto-mark", A_GOMARK }, 67 { "help", A_HELP }, 68 { "index-file", A_INDEX_FILE }, 69 { "invalid", A_UINVALID }, 70 { "left-scroll", A_LSHIFT }, 71 { "next-file", A_NEXT_FILE }, 72 { "next-tag", A_NEXT_TAG }, 73 { "noaction", A_NOACTION }, 74 { "no-scroll", A_LLSHIFT }, 75 { "percent", A_PERCENT }, 76 { "pipe", A_PIPE }, 77 { "prev-file", A_PREV_FILE }, 78 { "prev-tag", A_PREV_TAG }, 79 { "quit", A_QUIT }, 80 { "remove-file", A_REMOVE_FILE }, 81 { "repaint", A_REPAINT }, 82 { "repaint-flush", A_FREPAINT }, 83 { "repeat-search", A_AGAIN_SEARCH }, 84 { "repeat-search-all", A_T_AGAIN_SEARCH }, 85 { "reverse-search", A_REVERSE_SEARCH }, 86 { "reverse-search-all", A_T_REVERSE_SEARCH }, 87 { "right-scroll", A_RSHIFT }, 88 { "set-mark", A_SETMARK }, 89 { "set-mark-bottom", A_SETMARKBOT }, 90 { "shell", A_SHELL }, 91 { "status", A_STAT }, 92 { "toggle-flag", A_OPT_TOGGLE }, 93 { "toggle-option", A_OPT_TOGGLE }, 94 { "undo-hilite", A_UNDO_SEARCH }, 95 { "clear-search", A_CLR_SEARCH }, 96 { "version", A_VERSION }, 97 { "visual", A_VISUAL }, 98 { NULL, 0 } 99 }; 100 101 static struct lesskey_cmdname editnames[] = 102 { 103 { "back-complete", EC_B_COMPLETE }, 104 { "backspace", EC_BACKSPACE }, 105 { "delete", EC_DELETE }, 106 { "down", EC_DOWN }, 107 { "end", EC_END }, 108 { "expand", EC_EXPAND }, 109 { "forw-complete", EC_F_COMPLETE }, 110 { "home", EC_HOME }, 111 { "insert", EC_INSERT }, 112 { "invalid", EC_UINVALID }, 113 { "kill-line", EC_LINEKILL }, 114 { "abort", EC_ABORT }, 115 { "left", EC_LEFT }, 116 { "literal", EC_LITERAL }, 117 { "right", EC_RIGHT }, 118 { "up", EC_UP }, 119 { "word-backspace", EC_W_BACKSPACE }, 120 { "word-delete", EC_W_DELETE }, 121 { "word-left", EC_W_LEFT }, 122 { "word-right", EC_W_RIGHT }, 123 { NULL, 0 } 124 }; 125 126 /* 127 * Print a parse error message. 128 */ 129 static void 130 parse_error(fmt, arg1) 131 char *fmt; 132 char *arg1; 133 { 134 char buf[1024]; 135 int n = snprintf(buf, sizeof(buf), "%s: line %d: ", lesskey_file, linenum); 136 if (n >= 0 && n < sizeof(buf)) 137 snprintf(buf+n, sizeof(buf)-n, fmt, arg1); 138 ++errors; 139 lesskey_parse_error(buf); 140 } 141 142 /* 143 * Initialize lesskey_tables. 144 */ 145 static void 146 init_tables(tables) 147 struct lesskey_tables *tables; 148 { 149 tables->currtable = &tables->cmdtable; 150 151 tables->cmdtable.names = cmdnames; 152 tables->cmdtable.is_var = 0; 153 xbuf_init(&tables->cmdtable.buf); 154 155 tables->edittable.names = editnames; 156 tables->edittable.is_var = 0; 157 xbuf_init(&tables->edittable.buf); 158 159 tables->vartable.names = NULL; 160 tables->vartable.is_var = 1; 161 xbuf_init(&tables->vartable.buf); 162 } 163 164 #define CHAR_STRING_LEN 8 165 166 static char * 167 char_string(buf, ch, lit) 168 char *buf; 169 int ch; 170 int lit; 171 { 172 if (lit || (ch >= 0x20 && ch < 0x7f)) 173 { 174 buf[0] = ch; 175 buf[1] = '\0'; 176 } else 177 { 178 snprintf(buf, CHAR_STRING_LEN, "\\x%02x", ch); 179 } 180 return buf; 181 } 182 183 /* 184 * Increment char pointer by one up to terminating nul byte. 185 */ 186 static char * 187 increment_pointer(p) 188 char *p; 189 { 190 if (*p == '\0') 191 return p; 192 return p+1; 193 } 194 195 /* 196 * Parse one character of a string. 197 */ 198 static char * 199 tstr(pp, xlate) 200 char **pp; 201 int xlate; 202 { 203 char *p; 204 char ch; 205 int i; 206 static char buf[CHAR_STRING_LEN]; 207 static char tstr_control_k[] = 208 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' }; 209 210 p = *pp; 211 switch (*p) 212 { 213 case '\\': 214 ++p; 215 switch (*p) 216 { 217 case '0': case '1': case '2': case '3': 218 case '4': case '5': case '6': case '7': 219 /* 220 * Parse an octal number. 221 */ 222 ch = 0; 223 i = 0; 224 do 225 ch = 8*ch + (*p - '0'); 226 while (*++p >= '0' && *p <= '7' && ++i < 3); 227 *pp = p; 228 if (xlate && ch == CONTROL('K')) 229 return tstr_control_k; 230 return char_string(buf, ch, 1); 231 case 'b': 232 *pp = p+1; 233 return ("\b"); 234 case 'e': 235 *pp = p+1; 236 return char_string(buf, ESC, 1); 237 case 'n': 238 *pp = p+1; 239 return ("\n"); 240 case 'r': 241 *pp = p+1; 242 return ("\r"); 243 case 't': 244 *pp = p+1; 245 return ("\t"); 246 case 'k': 247 if (xlate) 248 { 249 switch (*++p) 250 { 251 case 'b': ch = SK_BACKSPACE; break; 252 case 'B': ch = SK_CTL_BACKSPACE; break; 253 case 'd': ch = SK_DOWN_ARROW; break; 254 case 'D': ch = SK_PAGE_DOWN; break; 255 case 'e': ch = SK_END; break; 256 case 'h': ch = SK_HOME; break; 257 case 'i': ch = SK_INSERT; break; 258 case 'l': ch = SK_LEFT_ARROW; break; 259 case 'L': ch = SK_CTL_LEFT_ARROW; break; 260 case 'r': ch = SK_RIGHT_ARROW; break; 261 case 'R': ch = SK_CTL_RIGHT_ARROW; break; 262 case 't': ch = SK_BACKTAB; break; 263 case 'u': ch = SK_UP_ARROW; break; 264 case 'U': ch = SK_PAGE_UP; break; 265 case 'x': ch = SK_DELETE; break; 266 case 'X': ch = SK_CTL_DELETE; break; 267 case '1': ch = SK_F1; break; 268 default: 269 parse_error("invalid escape sequence \"\\k%s\"", char_string(buf, *p, 0)); 270 *pp = increment_pointer(p); 271 return (""); 272 } 273 *pp = p+1; 274 buf[0] = SK_SPECIAL_KEY; 275 buf[1] = ch; 276 buf[2] = 6; 277 buf[3] = 1; 278 buf[4] = 1; 279 buf[5] = 1; 280 buf[6] = '\0'; 281 return (buf); 282 } 283 /* FALLTHRU */ 284 default: 285 /* 286 * Backslash followed by any other char 287 * just means that char. 288 */ 289 *pp = increment_pointer(p); 290 char_string(buf, *p, 1); 291 if (xlate && buf[0] == CONTROL('K')) 292 return tstr_control_k; 293 return (buf); 294 } 295 case '^': 296 /* 297 * Caret means CONTROL. 298 */ 299 *pp = increment_pointer(p+1); 300 char_string(buf, CONTROL(p[1]), 1); 301 if (xlate && buf[0] == CONTROL('K')) 302 return tstr_control_k; 303 return (buf); 304 } 305 *pp = increment_pointer(p); 306 char_string(buf, *p, 1); 307 if (xlate && buf[0] == CONTROL('K')) 308 return tstr_control_k; 309 return (buf); 310 } 311 312 static int 313 issp(ch) 314 char ch; 315 { 316 return (ch == ' ' || ch == '\t'); 317 } 318 319 /* 320 * Skip leading spaces in a string. 321 */ 322 static char * 323 skipsp(s) 324 char *s; 325 { 326 while (issp(*s)) 327 s++; 328 return (s); 329 } 330 331 /* 332 * Skip non-space characters in a string. 333 */ 334 static char * 335 skipnsp(s) 336 char *s; 337 { 338 while (*s != '\0' && !issp(*s)) 339 s++; 340 return (s); 341 } 342 343 /* 344 * Clean up an input line: 345 * strip off the trailing newline & any trailing # comment. 346 */ 347 static char * 348 clean_line(s) 349 char *s; 350 { 351 int i; 352 353 s = skipsp(s); 354 for (i = 0; s[i] != '\0' && s[i] != '\n' && s[i] != '\r'; i++) 355 if (s[i] == '#' && (i == 0 || s[i-1] != '\\')) 356 break; 357 s[i] = '\0'; 358 return (s); 359 } 360 361 /* 362 * Add a byte to the output command table. 363 */ 364 static void 365 add_cmd_char(c, tables) 366 int c; 367 struct lesskey_tables *tables; 368 { 369 xbuf_add(&tables->currtable->buf, c); 370 } 371 372 static void 373 erase_cmd_char(tables) 374 struct lesskey_tables *tables; 375 { 376 xbuf_pop(&tables->currtable->buf); 377 } 378 379 /* 380 * Add a string to the output command table. 381 */ 382 static void 383 add_cmd_str(s, tables) 384 char *s; 385 struct lesskey_tables *tables; 386 { 387 for ( ; *s != '\0'; s++) 388 add_cmd_char(*s, tables); 389 } 390 391 /* 392 * Does a given version number match the running version? 393 * Operator compares the running version to the given version. 394 */ 395 static int 396 match_version(op, ver) 397 char op; 398 int ver; 399 { 400 switch (op) 401 { 402 case '>': return less_version > ver; 403 case '<': return less_version < ver; 404 case '+': return less_version >= ver; 405 case '-': return less_version <= ver; 406 case '=': return less_version == ver; 407 case '!': return less_version != ver; 408 default: return 0; /* cannot happen */ 409 } 410 } 411 412 /* 413 * Handle a #version line. 414 * If the version matches, return the part of the line that should be executed. 415 * Otherwise, return NULL. 416 */ 417 static char * 418 version_line(s, tables) 419 char *s; 420 struct lesskey_tables *tables; 421 { 422 char op; 423 int ver; 424 char *e; 425 char buf[CHAR_STRING_LEN]; 426 427 s += strlen("#version"); 428 s = skipsp(s); 429 op = *s++; 430 /* Simplify 2-char op to one char. */ 431 switch (op) 432 { 433 case '<': if (*s == '=') { s++; op = '-'; } break; 434 case '>': if (*s == '=') { s++; op = '+'; } break; 435 case '=': if (*s == '=') { s++; } break; 436 case '!': if (*s == '=') { s++; } break; 437 default: 438 parse_error("invalid operator '%s' in #version line", char_string(buf, op, 0)); 439 return (NULL); 440 } 441 s = skipsp(s); 442 ver = lstrtoi(s, &e); 443 if (e == s) 444 { 445 parse_error("non-numeric version number in #version line", ""); 446 return (NULL); 447 } 448 if (!match_version(op, ver)) 449 return (NULL); 450 return (e); 451 } 452 453 /* 454 * See if we have a special "control" line. 455 */ 456 static char * 457 control_line(s, tables) 458 char *s; 459 struct lesskey_tables *tables; 460 { 461 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)) == 0) 462 463 if (PREFIX(s, "#line-edit")) 464 { 465 tables->currtable = &tables->edittable; 466 return (NULL); 467 } 468 if (PREFIX(s, "#command")) 469 { 470 tables->currtable = &tables->cmdtable; 471 return (NULL); 472 } 473 if (PREFIX(s, "#env")) 474 { 475 tables->currtable = &tables->vartable; 476 return (NULL); 477 } 478 if (PREFIX(s, "#stop")) 479 { 480 add_cmd_char('\0', tables); 481 add_cmd_char(A_END_LIST, tables); 482 return (NULL); 483 } 484 if (PREFIX(s, "#version")) 485 { 486 return (version_line(s, tables)); 487 } 488 return (s); 489 } 490 491 /* 492 * Find an action, given the name of the action. 493 */ 494 static int 495 findaction(actname, tables) 496 char *actname; 497 struct lesskey_tables *tables; 498 { 499 int i; 500 501 for (i = 0; tables->currtable->names[i].cn_name != NULL; i++) 502 if (strcmp(tables->currtable->names[i].cn_name, actname) == 0) 503 return (tables->currtable->names[i].cn_action); 504 parse_error("unknown action: \"%s\"", actname); 505 return (A_INVALID); 506 } 507 508 /* 509 * Parse a line describing one key binding, of the form 510 * KEY ACTION [EXTRA] 511 * where KEY is the user key sequence, ACTION is the 512 * resulting less action, and EXTRA is an "extra" user 513 * key sequence injected after the action. 514 */ 515 static void 516 parse_cmdline(p, tables) 517 char *p; 518 struct lesskey_tables *tables; 519 { 520 char *actname; 521 int action; 522 char *s; 523 char c; 524 525 /* 526 * Parse the command string and store it in the current table. 527 */ 528 do 529 { 530 s = tstr(&p, 1); 531 add_cmd_str(s, tables); 532 } while (*p != '\0' && !issp(*p)); 533 /* 534 * Terminate the command string with a null byte. 535 */ 536 add_cmd_char('\0', tables); 537 538 /* 539 * Skip white space between the command string 540 * and the action name. 541 * Terminate the action name with a null byte. 542 */ 543 p = skipsp(p); 544 if (*p == '\0') 545 { 546 parse_error("missing action", ""); 547 return; 548 } 549 actname = p; 550 p = skipnsp(p); 551 c = *p; 552 *p = '\0'; 553 554 /* 555 * Parse the action name and store it in the current table. 556 */ 557 action = findaction(actname, tables); 558 559 /* 560 * See if an extra string follows the action name. 561 */ 562 *p = c; 563 p = skipsp(p); 564 if (*p == '\0') 565 { 566 add_cmd_char(action, tables); 567 } else 568 { 569 /* 570 * OR the special value A_EXTRA into the action byte. 571 * Put the extra string after the action byte. 572 */ 573 add_cmd_char(action | A_EXTRA, tables); 574 while (*p != '\0') 575 add_cmd_str(tstr(&p, 0), tables); 576 add_cmd_char('\0', tables); 577 } 578 } 579 580 /* 581 * Parse a variable definition line, of the form 582 * NAME = VALUE 583 */ 584 static void 585 parse_varline(line, tables) 586 char *line; 587 struct lesskey_tables *tables; 588 { 589 char *s; 590 char *p = line; 591 char *eq; 592 593 eq = strchr(line, '='); 594 if (eq != NULL && eq > line && eq[-1] == '+') 595 { 596 /* 597 * Rather ugly way of handling a += line. 598 * {{ Note that we ignore the variable name and 599 * just append to the previously defined variable. }} 600 */ 601 erase_cmd_char(tables); /* backspace over the final null */ 602 p = eq+1; 603 } else 604 { 605 do 606 { 607 s = tstr(&p, 0); 608 add_cmd_str(s, tables); 609 } while (*p != '\0' && !issp(*p) && *p != '='); 610 /* 611 * Terminate the variable name with a null byte. 612 */ 613 add_cmd_char('\0', tables); 614 p = skipsp(p); 615 if (*p++ != '=') 616 { 617 parse_error("missing = in variable definition", ""); 618 return; 619 } 620 add_cmd_char(EV_OK|A_EXTRA, tables); 621 } 622 p = skipsp(p); 623 while (*p != '\0') 624 { 625 s = tstr(&p, 0); 626 add_cmd_str(s, tables); 627 } 628 add_cmd_char('\0', tables); 629 } 630 631 /* 632 * Parse a line from the lesskey file. 633 */ 634 static void 635 parse_line(line, tables) 636 char *line; 637 struct lesskey_tables *tables; 638 { 639 char *p; 640 641 /* 642 * See if it is a control line. 643 */ 644 p = control_line(line, tables); 645 if (p == NULL) 646 return; 647 /* 648 * Skip leading white space. 649 * Replace the final newline with a null byte. 650 * Ignore blank lines and comments. 651 */ 652 p = clean_line(p); 653 if (*p == '\0') 654 return; 655 656 if (tables->currtable->is_var) 657 parse_varline(p, tables); 658 else 659 parse_cmdline(p, tables); 660 } 661 662 /* 663 * Parse a lesskey source file and store result in tables. 664 */ 665 int 666 parse_lesskey(infile, tables) 667 char *infile; 668 struct lesskey_tables *tables; 669 { 670 FILE *desc; 671 char line[1024]; 672 673 if (infile == NULL) 674 infile = homefile(DEF_LESSKEYINFILE); 675 lesskey_file = infile; 676 677 init_tables(tables); 678 errors = 0; 679 linenum = 0; 680 if (less_version == 0) 681 less_version = lstrtoi(version, NULL); 682 683 /* 684 * Open the input file. 685 */ 686 if (strcmp(infile, "-") == 0) 687 desc = stdin; 688 else if ((desc = fopen(infile, "r")) == NULL) 689 { 690 /* parse_error("cannot open lesskey file %s", infile); */ 691 return (-1); 692 } 693 694 /* 695 * Read and parse the input file, one line at a time. 696 */ 697 while (fgets(line, sizeof(line), desc) != NULL) 698 { 699 ++linenum; 700 parse_line(line, tables); 701 } 702 fclose(desc); 703 return (errors); 704 } 705