1 /* 2 * Copyright (C) 1984-2026 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 11 /* 12 * Routines to decode user commands. 13 * 14 * This is all table driven. 15 * A command table is a sequence of command descriptors. 16 * Each command descriptor is a sequence of bytes with the following format: 17 * <c1><c2>...<cN><0><action> 18 * The characters c1,c2,...,cN are the command string; that is, 19 * the characters which the user must type. 20 * It is terminated by a null <0> byte. 21 * The byte after the null byte is the action code associated 22 * with the command string. 23 * If an action byte is OR-ed with A_EXTRA, this indicates 24 * that the option byte is followed by an extra string. 25 * 26 * There may be many command tables. 27 * The first (default) table is built-in. 28 * Other tables are read in from "lesskey" files. 29 * All the tables are linked together and are searched in order. 30 */ 31 32 #include "less.h" 33 #include "cmd.h" 34 #include "lesskey.h" 35 36 extern int erase_char, erase2_char, kill_char; 37 extern int emouse; 38 extern int mouse_reverse; 39 extern int hshift; 40 extern int sc_height; 41 extern constant char *no_config; 42 43 #if USERFILE 44 /* "content" is lesskey source, never binary. */ 45 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar); 46 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar); 47 #endif /* USERFILE */ 48 49 #define SK(k) \ 50 SK_SPECIAL_KEY, (k), 6, 1, 1, 1 51 /* 52 * Command table is ordered roughly according to expected 53 * frequency of use, so the common commands are near the beginning. 54 */ 55 56 static unsigned char cmdtable[] = 57 { 58 '\r',0, A_F_LINE, 59 '\n',0, A_F_LINE, 60 'e',0, A_F_LINE, 61 'j',0, A_F_LINE, 62 SK(SK_DOWN_ARROW),0, A_F_LINE, 63 CONTROL('E'),0, A_F_LINE, 64 CONTROL('N'),0, A_F_LINE, 65 'k',0, A_B_LINE, 66 'y',0, A_B_LINE, 67 CONTROL('Y'),0, A_B_LINE, 68 SK(SK_CONTROL_K),0, A_B_LINE, 69 CONTROL('P'),0, A_B_LINE, 70 SK(SK_UP_ARROW),0, A_B_LINE, 71 'J',0, A_FF_LINE, 72 'K',0, A_BF_LINE, 73 'Y',0, A_BF_LINE, 74 'd',0, A_F_SCROLL, 75 CONTROL('D'),0, A_F_SCROLL, 76 'u',0, A_B_SCROLL, 77 CONTROL('U'),0, A_B_SCROLL, 78 ESC,'[','M',0, A_X11MOUSE_IN, 79 ESC,'[','<',0, A_X116MOUSE_IN, 80 ' ',0, A_F_SCREEN, 81 'f',0, A_F_SCREEN, 82 CONTROL('F'),0, A_F_SCREEN, 83 CONTROL('V'),0, A_F_SCREEN, 84 SK(SK_PAGE_DOWN),0, A_F_SCREEN, 85 'b',0, A_B_SCREEN, 86 CONTROL('B'),0, A_B_SCREEN, 87 ESC,'v',0, A_B_SCREEN, 88 SK(SK_PAGE_UP),0, A_B_SCREEN, 89 'z',0, A_F_WINDOW, 90 'w',0, A_B_WINDOW, 91 ESC,' ',0, A_FF_SCREEN, 92 ESC,'b',0, A_BF_SCREEN, 93 ESC,'j',0, A_F_NEWLINE, 94 ESC,'k',0, A_B_NEWLINE, 95 'F',0, A_F_FOREVER, 96 ESC,'f',0, A_F_FOREVER_BELL, 97 ESC,'F',0, A_F_UNTIL_HILITE, 98 'R',0, A_FREPAINT, 99 'r',0, A_REPAINT, 100 CONTROL('R'),0, A_REPAINT, 101 CONTROL('L'),0, A_REPAINT, 102 ESC,'u',0, A_UNDO_SEARCH, 103 ESC,'U',0, A_CLR_SEARCH, 104 'g',0, A_GOLINE, 105 SK(SK_HOME),0, A_GOLINE, 106 SK(SK_SHIFT_HOME),0, A_GOLINE|A_EXTRA, ESC,'{',0, 107 SK(SK_CTL_HOME),0, A_GOLINE|A_EXTRA, ESC,'{',0, 108 SK(SK_END),0, A_GOEND, 109 SK(SK_SHIFT_END),0, A_GOEND|A_EXTRA, ESC,'}',0, 110 SK(SK_CTL_END),0, A_GOEND|A_EXTRA, ESC,'}',0, 111 '<',0, A_GOLINE, 112 ESC,'<',0, A_GOLINE, 113 'p',0, A_PERCENT, 114 '%',0, A_PERCENT, 115 ESC,'(',0, A_LSHIFT, 116 ESC,')',0, A_RSHIFT, 117 ESC,'{',0, A_LLSHIFT, 118 ESC,'}',0, A_RRSHIFT, 119 SK(SK_RIGHT_ARROW),0, A_RSHIFT, 120 SK(SK_LEFT_ARROW),0, A_LSHIFT, 121 SK(SK_CTL_RIGHT_ARROW),0, A_RRSHIFT, 122 SK(SK_CTL_LEFT_ARROW),0, A_LLSHIFT, 123 SK(SK_SHIFT_RIGHT_ARROW),0, A_RRSHIFT, 124 SK(SK_SHIFT_LEFT_ARROW),0, A_LLSHIFT, 125 ESC,SK(SK_RIGHT_ARROW),0, A_RSHIFT_LIMIT, 126 ESC,']',0, A_RSHIFT_LIMIT, 127 '{',0, A_F_BRACKET|A_EXTRA, '{','}',0, 128 '}',0, A_B_BRACKET|A_EXTRA, '{','}',0, 129 '(',0, A_F_BRACKET|A_EXTRA, '(',')',0, 130 ')',0, A_B_BRACKET|A_EXTRA, '(',')',0, 131 '[',0, A_F_BRACKET|A_EXTRA, '[',']',0, 132 ']',0, A_B_BRACKET|A_EXTRA, '[',']',0, 133 ESC,CONTROL('F'),0, A_F_BRACKET, 134 ESC,CONTROL('B'),0, A_B_BRACKET, 135 'G',0, A_GOEND, 136 ESC,'G',0, A_GOEND_BUF, 137 ESC,'>',0, A_GOEND, 138 '>',0, A_GOEND, 139 'P',0, A_GOPOS, 140 141 '0',0, A_DIGIT, 142 '1',0, A_DIGIT, 143 '2',0, A_DIGIT, 144 '3',0, A_DIGIT, 145 '4',0, A_DIGIT, 146 '5',0, A_DIGIT, 147 '6',0, A_DIGIT, 148 '7',0, A_DIGIT, 149 '8',0, A_DIGIT, 150 '9',0, A_DIGIT, 151 '.',0, A_DIGIT, 152 153 '=',0, A_STAT, 154 CONTROL('G'),0, A_STAT, 155 ':','f',0, A_STAT, 156 '/',0, A_F_SEARCH, 157 '?',0, A_B_SEARCH, 158 ESC,'/',0, A_F_SEARCH|A_EXTRA, '*',0, 159 ESC,'?',0, A_B_SEARCH|A_EXTRA, '*',0, 160 'n',0, A_AGAIN_SEARCH, 161 ESC,'n',0, A_T_AGAIN_SEARCH, 162 'N',0, A_REVERSE_SEARCH, 163 ESC,'N',0, A_T_REVERSE_SEARCH, 164 '&',0, A_FILTER, 165 'm',0, A_SETMARK, 166 'M',0, A_SETMARKBOT, 167 ESC,'m',0, A_CLRMARK, 168 '\'',0, A_GOMARK, 169 CONTROL('X'),CONTROL('X'),0, A_GOMARK, 170 'E',0, A_EXAMINE, 171 ':','e',0, A_EXAMINE, 172 CONTROL('X'),CONTROL('V'),0, A_EXAMINE, 173 ':','n',0, A_NEXT_FILE, 174 ':','p',0, A_PREV_FILE, 175 CONTROL('O'),CONTROL('N'),0, A_OSC8_F_SEARCH, 176 CONTROL('O'),'n',0, A_OSC8_F_SEARCH, 177 CONTROL('O'),CONTROL('P'),0, A_OSC8_B_SEARCH, 178 CONTROL('O'),'p',0, A_OSC8_B_SEARCH, 179 CONTROL('O'),CONTROL('O'),0, A_OSC8_OPEN, 180 CONTROL('O'),'o',0, A_OSC8_OPEN, 181 CONTROL('O'),CONTROL('L'),0, A_OSC8_JUMP, 182 CONTROL('O'),'l',0, A_OSC8_JUMP, 183 't',0, A_NEXT_TAG, 184 'T',0, A_PREV_TAG, 185 ':','x',0, A_INDEX_FILE, 186 ':','d',0, A_REMOVE_FILE, 187 '-',0, A_OPT_TOGGLE, 188 ':','t',0, A_OPT_TOGGLE|A_EXTRA, 't',0, 189 's',0, A_OPT_TOGGLE|A_EXTRA, 'o',0, 190 '_',0, A_DISP_OPTION, 191 '|',0, A_PIPE, 192 'v',0, A_VISUAL, 193 '!',0, A_SHELL, 194 '#',0, A_PSHELL, 195 '+',0, A_FIRSTCMD, 196 197 SK(SK_PAD_U),0, A_B_LINE, 198 SK(SK_PAD_D),0, A_F_LINE, 199 SK(SK_PAD_R),0, A_RSHIFT, 200 SK(SK_PAD_L),0, A_LSHIFT, 201 SK(SK_PAD_UR),0, A_B_SCREEN, 202 SK(SK_PAD_UL),0, A_GOLINE, 203 SK(SK_PAD_DR),0, A_F_SCREEN, 204 SK(SK_PAD_DL),0, A_GOEND, 205 SK(SK_PAD_STAR),0, A_NOACTION|A_EXTRA, '*',0, 206 SK(SK_PAD_SLASH),0, A_NOACTION|A_EXTRA, '/',0, 207 SK(SK_PAD_DASH),0, A_NOACTION|A_EXTRA, '-',0, 208 SK(SK_PAD_PLUS),0, A_NOACTION|A_EXTRA, '+',0, 209 SK(SK_PAD_DOT),0, A_NOACTION|A_EXTRA, '.',0, 210 SK(SK_PAD_COMMA),0, A_NOACTION, 211 SK(SK_PAD_ZERO),0, A_NOACTION|A_EXTRA, '0',0, 212 SK(SK_PAD_CENTER),0, A_NOACTION, 213 SK(SK_CTL_UP_ARROW),0, A_NOACTION, 214 SK(SK_CTL_DOWN_ARROW),0, A_NOACTION, 215 SK(SK_SHIFT_UP_ARROW),0, A_NOACTION, 216 SK(SK_SHIFT_DOWN_ARROW),0, A_NOACTION, 217 SK(SK_DELETE),0, A_NOACTION, 218 SK(SK_CTL_DELETE),0, A_NOACTION, 219 SK(SK_SHIFT_DELETE),0, A_NOACTION, 220 SK(SK_INSERT),0, A_NOACTION, 221 SK(SK_BACKTAB),0, A_NOACTION, 222 SK(SK_BACKSPACE),0, A_NOACTION, 223 SK(SK_CTL_BACKSPACE),0, A_NOACTION, 224 SK(SK_SHIFT_PAGE_UP),0, A_NOACTION, 225 SK(SK_SHIFT_PAGE_DOWN),0, A_NOACTION, 226 SK(SK_CTL_PAGE_UP),0, A_NOACTION, 227 SK(SK_CTL_PAGE_DOWN),0, A_NOACTION, 228 '\b',0, A_NOACTION, 229 '\177',0, A_NOACTION, 230 231 ESC,'[','2','0','0','~',0, A_START_PASTE, 232 ESC,'[','2','0','1','~',0, A_END_PASTE, 233 234 'H',0, A_HELP, 235 'h',0, A_HELP, 236 SK(SK_F1),0, A_HELP, 237 'V',0, A_VERSION, 238 'q',0, A_QUIT, 239 'Q',0, A_QUIT, 240 ':','q',0, A_QUIT, 241 ':','Q',0, A_QUIT, 242 'Z','Z',0, A_QUIT 243 }; 244 245 static unsigned char edittable[] = 246 { 247 '\t',0, EC_F_COMPLETE, /* TAB */ 248 '\17',0, EC_B_COMPLETE, /* BACKTAB */ 249 SK(SK_BACKTAB),0, EC_B_COMPLETE, /* BACKTAB */ 250 ESC,'\t',0, EC_B_COMPLETE, /* ESC TAB */ 251 CONTROL('L'),0, EC_EXPAND, /* CTRL-L */ 252 CONTROL('V'),0, EC_LITERAL, /* BACKSLASH */ 253 CONTROL('A'),0, EC_LITERAL, /* BACKSLASH */ 254 ESC,'l',0, EC_RIGHT, /* ESC l */ 255 SK(SK_RIGHT_ARROW),0, EC_RIGHT, /* RIGHTARROW */ 256 ESC,'h',0, EC_LEFT, /* ESC h */ 257 SK(SK_LEFT_ARROW),0, EC_LEFT, /* LEFTARROW */ 258 ESC,'b',0, EC_W_LEFT, /* ESC b */ 259 ESC,SK(SK_LEFT_ARROW),0, EC_W_LEFT, /* ESC LEFTARROW */ 260 SK(SK_CTL_LEFT_ARROW),0, EC_W_LEFT, /* CTRL-LEFTARROW */ 261 ESC,'w',0, EC_W_RIGHT, /* ESC w */ 262 ESC,SK(SK_RIGHT_ARROW),0, EC_W_RIGHT, /* ESC RIGHTARROW */ 263 SK(SK_CTL_RIGHT_ARROW),0, EC_W_RIGHT, /* CTRL-RIGHTARROW */ 264 ESC,'i',0, EC_INSERT, /* ESC i */ 265 SK(SK_INSERT),0, EC_INSERT, /* INSERT */ 266 ESC,'x',0, EC_DELETE, /* ESC x */ 267 SK(SK_DELETE),0, EC_DELETE, /* DELETE */ 268 ESC,'X',0, EC_W_DELETE, /* ESC X */ 269 ESC,SK(SK_DELETE),0, EC_W_DELETE, /* ESC DELETE */ 270 SK(SK_CTL_DELETE),0, EC_W_DELETE, /* CTRL-DELETE */ 271 SK(SK_CTL_BACKSPACE),0, EC_W_BACKSPACE, /* CTRL-BACKSPACE */ 272 ESC,SK(SK_BACKSPACE),0, EC_W_BACKSPACE, /* ESC BACKSPACE */ 273 ESC,'0',0, EC_HOME, /* ESC 0 */ 274 SK(SK_HOME),0, EC_HOME, /* HOME */ 275 SK(SK_SHIFT_HOME),0, EC_HOME, /* SHIFT-HOME */ 276 SK(SK_CTL_HOME),0, EC_HOME, /* CTRL-HOME */ 277 ESC,'$',0, EC_END, /* ESC $ */ 278 SK(SK_END),0, EC_END, /* END */ 279 SK(SK_SHIFT_END),0, EC_END, /* SHIFT-END */ 280 SK(SK_CTL_END),0, EC_END, /* CTRL-END */ 281 ESC,'k',0, EC_UP, /* ESC k */ 282 SK(SK_UP_ARROW),0, EC_UP, /* UPARROW */ 283 ESC,'j',0, EC_DOWN, /* ESC j */ 284 SK(SK_DOWN_ARROW),0, EC_DOWN, /* DOWNARROW */ 285 CONTROL('G'),0, EC_ABORT, /* CTRL-G */ 286 SK(SK_PAD_U),0, EC_UP, 287 SK(SK_PAD_D),0, EC_DOWN, 288 SK(SK_PAD_R),0, EC_RIGHT, 289 SK(SK_PAD_L),0, EC_LEFT, 290 SK(SK_PAD_UR),0, A_NOACTION, 291 SK(SK_PAD_UL),0, EC_HOME, 292 SK(SK_PAD_DR),0, A_NOACTION, 293 SK(SK_PAD_DL),0, EC_END, 294 SK(SK_PAD_STAR),0, A_NOACTION|A_EXTRA, '*',0, 295 SK(SK_PAD_SLASH),0, A_NOACTION|A_EXTRA, '/',0, 296 SK(SK_PAD_DASH),0, A_NOACTION|A_EXTRA, '-',0, 297 SK(SK_PAD_PLUS),0, A_NOACTION|A_EXTRA, '+',0, 298 SK(SK_PAD_DOT),0, A_NOACTION|A_EXTRA, '.',0, 299 SK(SK_PAD_COMMA),0, A_NOACTION|A_EXTRA, ',',0, 300 SK(SK_PAD_ZERO),0, A_NOACTION|A_EXTRA, '0',0, 301 SK(SK_PAD_CENTER),0, A_NOACTION, 302 SK(SK_CTL_UP_ARROW),0, A_NOACTION, 303 SK(SK_CTL_DOWN_ARROW),0, A_NOACTION, 304 SK(SK_SHIFT_UP_ARROW),0, A_NOACTION, 305 SK(SK_SHIFT_DOWN_ARROW),0, A_NOACTION, 306 SK(SK_SHIFT_DELETE),0, A_NOACTION, 307 SK(SK_PAGE_UP),0, A_NOACTION, 308 SK(SK_PAGE_DOWN),0, A_NOACTION, 309 SK(SK_SHIFT_LEFT_ARROW),0, A_NOACTION, 310 SK(SK_SHIFT_RIGHT_ARROW),0, A_NOACTION, 311 SK(SK_SHIFT_PAGE_UP),0, A_NOACTION, 312 SK(SK_SHIFT_PAGE_DOWN),0, A_NOACTION, 313 SK(SK_CTL_PAGE_UP),0, A_NOACTION, 314 SK(SK_CTL_PAGE_DOWN),0, A_NOACTION, 315 ESC,'[','M',0, EC_X11MOUSE, /* X11 mouse report */ 316 ESC,'[','<',0, EC_X116MOUSE, /* X11 1006 mouse report */ 317 ESC,'[','2','0','0','~',0, A_START_PASTE, /* open paste bracket */ 318 ESC,'[','2','0','1','~',0, A_END_PASTE, /* close paste bracket */ 319 }; 320 321 /* 322 * Default environment variables. 323 * Note: "\201" is (A_EXTRA|EV_OK). 324 */ 325 static char dflt_vartable[] = 326 { 327 "LESS_OSC8_OPEN_ANY\0\201" 328 #ifdef LIBEXECDIR 329 "-" LIBEXECDIR "/less-osc8-open" 330 #else 331 "-less-osc8-open" 332 #endif 333 "\0" 334 }; 335 336 /* 337 * Structure to support a list of command tables. 338 */ 339 struct tablelist 340 { 341 struct tablelist *t_next; 342 unsigned char *t_start; 343 unsigned char *t_end; 344 }; 345 346 /* 347 * List of command tables and list of line-edit tables. 348 */ 349 static struct tablelist *list_fcmd_tables = NULL; 350 static struct tablelist *list_ecmd_tables = NULL; 351 static struct tablelist *list_var_tables = NULL; 352 static struct tablelist *list_sysvar_tables = NULL; 353 354 #if USERFILE 355 static struct tablelist * find_stop_table(struct tablelist *t); 356 #endif 357 358 /* 359 * Expand special key abbreviations in a command table. 360 */ 361 static void expand_special_keys(unsigned char *table, size_t len) 362 { 363 unsigned char *fm; 364 unsigned char *to; 365 int a; 366 constant char *repl; 367 size_t klen; 368 369 for (fm = table; fm < table + len; ) 370 { 371 /* 372 * Rewrite each command in the table with any 373 * special key abbreviations expanded. 374 */ 375 for (to = fm; fm < table + len && *fm != '\0'; ) 376 { 377 if (*fm != SK_SPECIAL_KEY) 378 { 379 *to++ = *fm++; 380 continue; 381 } 382 /* 383 * After SK_SPECIAL_KEY, next byte is the type 384 * of special key (one of the SK_* constants), 385 * and the byte after that is the number of bytes, 386 * N, reserved by the abbreviation (including the 387 * SK_SPECIAL_KEY and key type bytes). 388 * Replace all N bytes with the actual bytes 389 * output by the special key on this terminal. 390 */ 391 repl = special_key_str(fm[1]); 392 klen = fm[2]; 393 fm += klen; 394 if (klen < 3) 395 { 396 /* Malformed entry; N cannot be less than 3. 397 * Skip this entry and (for simplicity) the rest of the table. */ 398 return; 399 } 400 if (repl == NULL || strlen(repl) > klen) 401 repl = "\377"; 402 if (to + strlen(repl) + 1 > table + len) 403 { 404 /* Replacement string won't fit in table. 405 * This cannot happen unless klen is wrong. */ 406 return; 407 } 408 while (*repl != '\0') 409 *to++ = (unsigned char) *repl++; /*{{type-issue}}*/ 410 } 411 if (fm + 2 > table + len) 412 return; /* last entry is truncated */ 413 *to++ = '\0'; 414 /* 415 * Fill any unused bytes between end of command and 416 * the action byte with A_SKIP. 417 */ 418 while (to <= fm) 419 *to++ = A_SKIP; 420 fm++; 421 a = *fm++; 422 if (a & A_EXTRA) 423 { 424 while (fm < table + len && *fm++ != '\0') 425 continue; 426 } 427 } 428 } 429 430 /* 431 * Expand special key abbreviations in a list of command tables. 432 */ 433 static void expand_cmd_table(struct tablelist *tlist) 434 { 435 struct tablelist *t; 436 for (t = tlist; t != NULL; t = t->t_next) 437 { 438 expand_special_keys(t->t_start, ptr_diff(t->t_end, t->t_start)); 439 } 440 } 441 442 /* 443 * Expand special key abbreviations in all command tables. 444 */ 445 public void expand_cmd_tables(void) 446 { 447 expand_cmd_table(list_fcmd_tables); 448 expand_cmd_table(list_ecmd_tables); 449 expand_cmd_table(list_var_tables); 450 expand_cmd_table(list_sysvar_tables); 451 } 452 453 /* 454 * Add a command table. 455 */ 456 static int add_cmd_table(struct tablelist **tlist, unsigned char *buf, size_t len) 457 { 458 struct tablelist *t; 459 460 if (len == 0) 461 return (0); 462 /* 463 * Allocate a tablelist structure, initialize it, 464 * and link it into the list of tables. 465 */ 466 if ((t = (struct tablelist *) 467 calloc(1, sizeof(struct tablelist))) == NULL) 468 { 469 return (-1); 470 } 471 t->t_start = buf; 472 t->t_end = buf + len; 473 t->t_next = NULL; 474 if (*tlist == NULL) 475 *tlist = t; 476 else 477 { 478 struct tablelist *e; 479 for (e = *tlist; e->t_next != NULL; e = e->t_next) 480 continue; 481 e->t_next = t; 482 } 483 return (0); 484 } 485 486 /* 487 * Add a command table. 488 */ 489 static void add_fcmd_table(unsigned char *buf, size_t len) 490 { 491 if (add_cmd_table(&list_fcmd_tables, buf, len) < 0) 492 error(LM(some_commands_disabled), NULL_PARG); 493 } 494 495 /* 496 * Add an editing command table. 497 */ 498 static void add_ecmd_table(unsigned char *buf, size_t len) 499 { 500 if (add_cmd_table(&list_ecmd_tables, buf, len) < 0) 501 error(LM(some_edit_commands_disabled), NULL_PARG); 502 } 503 504 /* 505 * Add an environment variable table. 506 */ 507 static void add_var_table(struct tablelist **tlist, mutable unsigned char *buf, size_t len) 508 { 509 struct xbuffer xbuf; 510 511 xbuf_init(&xbuf); 512 expand_evars((mutable char*)buf, len, &xbuf); /*{{unsigned-issue}}*/ 513 /* {{ We leak the table in buf. expand_evars scribbled in it so it's useless anyway. }} */ 514 if (add_cmd_table(tlist, xbuf.data, xbuf.end) < 0) 515 error(LM(environment_variables_from_lesskey_file_unavailable), NULL_PARG); 516 } 517 518 static void add_uvar_table(unsigned char *buf, size_t len) 519 { 520 add_var_table(&list_var_tables, buf, len); 521 } 522 523 static void add_sysvar_table(unsigned char *buf, size_t len) 524 { 525 add_var_table(&list_sysvar_tables, buf, len); 526 } 527 528 /* 529 * Initialize the command lists. 530 */ 531 public void init_cmds(void) 532 { 533 unsigned char *udflt_vartable = (unsigned char *) dflt_vartable; 534 535 /* 536 * Add the default command tables. 537 */ 538 add_fcmd_table(cmdtable, sizeof(cmdtable)); 539 add_ecmd_table(edittable, sizeof(edittable)); 540 add_sysvar_table(udflt_vartable, sizeof(dflt_vartable)); 541 #if USERFILE 542 #ifdef BINDIR /* For backwards compatibility */ 543 /* Try to add tables in the OLD system lesskey file. */ 544 add_hometable(lesskey, NULL, BINDIR "/.sysless", TRUE); 545 #endif 546 /* 547 * Try to load lesskey source file or binary file. 548 * If the source file succeeds, don't load binary file. 549 * The binary file is likely to have been generated from 550 * a (possibly out of date) copy of the src file, 551 * so loading it is at best redundant. 552 */ 553 /* 554 * Try to add tables in system lesskey src file. 555 */ 556 #if HAVE_LESSKEYSRC 557 if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, TRUE) != 0) 558 #endif 559 { 560 /* 561 * Try to add the tables in the system lesskey binary file. 562 */ 563 add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, TRUE); 564 } 565 /* 566 * Try to add tables in the lesskey src file "$HOME/.lesskey". 567 */ 568 #if HAVE_LESSKEYSRC 569 if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, FALSE) != 0) 570 #endif 571 { 572 /* 573 * Try to add the tables in the standard lesskey binary file "$HOME/.less". 574 */ 575 add_hometable(lesskey, "LESSKEY", LESSKEYFILE, FALSE); 576 } 577 578 add_content_table(lesskey_content, "LESSKEY_CONTENT_SYSTEM", TRUE); 579 add_content_table(lesskey_content, "LESSKEY_CONTENT", FALSE); 580 581 /* 582 * If any command table contains a #stop directive, discard all other tables. 583 */ 584 { 585 struct tablelist *t = find_stop_table(list_fcmd_tables); 586 if (t != NULL) 587 { 588 t->t_next = NULL; 589 list_fcmd_tables = t; 590 } 591 } 592 #endif /* USERFILE */ 593 } 594 595 /* 596 * Remove the last command table in a list. 597 */ 598 static void pop_cmd_table(struct tablelist **tlist) 599 { 600 struct tablelist *t; 601 if (*tlist == NULL) 602 return; 603 if ((*tlist)->t_next == NULL) 604 { 605 t = *tlist; 606 *tlist = NULL; 607 } else 608 { 609 struct tablelist *e; 610 for (e = *tlist; e->t_next->t_next != NULL; e = e->t_next) 611 continue; 612 t = e->t_next; 613 e->t_next = NULL; 614 } 615 free(t); 616 } 617 618 /* 619 * Return action for a mouse wheel down event. 620 */ 621 static int mouse_wheel_down(void) 622 { 623 if (!(emouse & EMOUSE_VSCROLL)) 624 return A_NOACTION; 625 return mouse_reverse ? A_B_MOUSE : A_F_MOUSE; 626 } 627 628 /* 629 * Return action for a mouse wheel up event. 630 */ 631 static int mouse_wheel_up(void) 632 { 633 if (!(emouse & EMOUSE_VSCROLL)) 634 return A_NOACTION; 635 return mouse_reverse ? A_F_MOUSE : A_B_MOUSE; 636 } 637 638 /* 639 * Return action for a mouse wheel left event. 640 */ 641 static int mouse_wheel_left(void) 642 { 643 if (!(emouse & EMOUSE_HSCROLL)) 644 return A_NOACTION; 645 return mouse_reverse ? A_R_MOUSE : A_L_MOUSE; 646 } 647 648 /* 649 * Return action for a mouse wheel right event. 650 */ 651 static int mouse_wheel_right(void) 652 { 653 if (!(emouse & EMOUSE_HSCROLL)) 654 return A_NOACTION; 655 return mouse_reverse ? A_L_MOUSE : A_R_MOUSE; 656 } 657 658 /* 659 * Return action for the left mouse button trigger. 660 */ 661 static int mouse_button_left(int x, int y, lbool down, lbool drag) 662 { 663 static int last_drag_x = -1; 664 static int last_drag_y = -1; 665 static int last_click_y = -1; 666 667 if (down && !drag) 668 { 669 last_drag_x = x; 670 last_drag_y = last_click_y = y; 671 } 672 if (drag) 673 { 674 if ((emouse & EMOUSE_HDRAG) && last_drag_x >= 0 && x != last_drag_x) { 675 /* Drag text left/right */ 676 pos_rehead(FALSE); 677 if (hshift < x - last_drag_x) 678 hshift = 0; 679 else 680 hshift -= x - last_drag_x; 681 screen_trashed(); 682 cmd_exec(); 683 last_drag_x = x; 684 } 685 if ((emouse & EMOUSE_VDRAG) && last_drag_y >= 0) { 686 /* Drag text up/down */ 687 if (y > last_drag_y) 688 { 689 cmd_exec(); 690 backward(y - last_drag_y, FALSE, FALSE, FALSE); 691 last_drag_y = y; 692 } else if (y < last_drag_y) 693 { 694 cmd_exec(); 695 forward(last_drag_y - y, FALSE, FALSE, FALSE); 696 last_drag_y = y; 697 } 698 } 699 } else if ((emouse & EMOUSE_LCLICK) && !down) 700 { 701 #if OSC8_LINK 702 if (secure_allow(SF_OSC8_OPEN)) 703 { 704 cmd_exec(); 705 if (osc8_click(y, x)) 706 return (A_NOACTION); 707 } 708 #else 709 (void) x; 710 #endif /* OSC8_LINK */ 711 if (y < sc_height-1 && y == last_click_y) 712 { 713 cmd_exec(); 714 setmark('#', y, 0); 715 screen_trashed(); 716 } 717 } 718 return (A_NOACTION); 719 } 720 721 /* 722 * Return action for the right mouse button trigger. 723 */ 724 static int mouse_button_right(int x, int y, lbool down, lbool drag) 725 { 726 (void) x; (void) drag; 727 /* 728 * {{ unlike mouse_button_left, we could return an action, 729 * but keep it near mouse_button_left for readability. }} 730 */ 731 if (!(emouse & EMOUSE_RCLICK)) 732 return (A_NOACTION); 733 if (!down && y < sc_height-1) 734 { 735 gomark('#', 0); 736 screen_trashed(); 737 } 738 return (A_NOACTION); 739 } 740 741 /* 742 * Read a decimal integer. Return the integer and set *pterm to the terminating char. 743 */ 744 static int getcc_int(char *pterm) 745 { 746 int num = 0; 747 int digits = 0; 748 for (;;) 749 { 750 char ch = getcc(); 751 if (ch < '0' || ch > '9') 752 { 753 if (pterm != NULL) *pterm = ch; 754 if (digits == 0) 755 return (-1); 756 return (num); 757 } 758 if (ckd_mul(&num, num, 10) || ckd_add(&num, num, ch - '0')) 759 return -1; 760 ++digits; 761 } 762 } 763 764 static int x11mouse_button(int btn, int x, int y, lbool down, lbool drag) 765 { 766 switch (btn) { 767 case X11MOUSE_BUTTON1: 768 return mouse_button_left(x, y, down, drag); 769 /* is BUTTON2 the rightmost with 2-buttons mouse? */ 770 case X11MOUSE_BUTTON2: 771 case X11MOUSE_BUTTON3: 772 return mouse_button_right(x, y, down, drag); 773 } 774 return (A_NOACTION); 775 } 776 777 /* 778 * Read suffix of mouse input and return the action to take. 779 * The prefix ("\e[M") has already been read. 780 */ 781 static int x11mouse_action(lbool skip) 782 { 783 static int prev_b = X11MOUSE_BUTTON_REL; 784 int x, y; 785 int b = getcc() - X11MOUSE_OFFSET; 786 lbool drag = ((b & X11MOUSE_DRAG) != 0); 787 b &= ~X11MOUSE_DRAG; 788 x = getcc() - X11MOUSE_OFFSET-1; 789 y = getcc() - X11MOUSE_OFFSET-1; 790 if (skip) 791 return (A_NOACTION); 792 switch (b) { 793 case X11MOUSE_WHEEL_DOWN: 794 return mouse_wheel_down(); 795 case X11MOUSE_WHEEL_UP: 796 return mouse_wheel_up(); 797 case X11MOUSE_WHEEL_LEFT: 798 return mouse_wheel_left(); 799 case X11MOUSE_WHEEL_RIGHT: 800 return mouse_wheel_right(); 801 case X11MOUSE_BUTTON1: 802 case X11MOUSE_BUTTON2: 803 case X11MOUSE_BUTTON3: 804 prev_b = b; 805 return x11mouse_button(b, x, y, TRUE, drag); 806 case X11MOUSE_BUTTON_REL: /* button up */ 807 return x11mouse_button(prev_b, x, y, FALSE, drag); 808 } 809 return (A_NOACTION); 810 } 811 812 /* 813 * Read suffix of mouse input and return the action to take. 814 * The prefix ("\e[<") has already been read. 815 */ 816 static int x116mouse_action(lbool skip) 817 { 818 char ch; 819 int x, y; 820 int b = getcc_int(&ch); 821 lbool drag = ((b & X11MOUSE_DRAG) != 0); 822 b &= ~X11MOUSE_DRAG; 823 if (b < 0 || ch != ';') return (A_NOACTION); 824 x = getcc_int(&ch) - 1; 825 if (x < 0 || ch != ';') return (A_NOACTION); 826 y = getcc_int(&ch) - 1; 827 if (y < 0) return (A_NOACTION); 828 if (skip) 829 return (A_NOACTION); 830 switch (b) { 831 case X11MOUSE_WHEEL_DOWN: 832 return mouse_wheel_down(); 833 case X11MOUSE_WHEEL_UP: 834 return mouse_wheel_up(); 835 case X11MOUSE_WHEEL_LEFT: 836 return mouse_wheel_left(); 837 case X11MOUSE_WHEEL_RIGHT: 838 return mouse_wheel_right(); 839 case X11MOUSE_BUTTON1: 840 case X11MOUSE_BUTTON2: 841 case X11MOUSE_BUTTON3: { 842 lbool down = (ch == 'M'); 843 lbool up = (ch == 'm'); 844 if (up || down) 845 return x11mouse_button(b, x, y, down, drag); 846 break; } 847 } 848 return (A_NOACTION); 849 } 850 851 /* 852 * Return the largest N such that the first N chars of goal 853 * are equal to the last N chars of str. 854 */ 855 static size_t cmd_match(constant char *goal, constant char *str) 856 { 857 size_t slen = strlen(str); 858 size_t len; 859 for (len = slen; len > 0; len--) 860 if (strncmp(str + slen - len, goal, len) == 0) 861 break; 862 return len; 863 } 864 865 /* 866 * Skip to next null byte in entry, and return pointer to it. 867 * Return NULL if no null byte is found before end. 868 */ 869 static constant unsigned char * skip_to_null(constant unsigned char *entry, constant unsigned char *end) 870 { 871 for (;;) 872 { 873 if (entry >= end) 874 return NULL; 875 if (*entry == '\0') 876 return entry; 877 ++entry; 878 } 879 } 880 881 /* 882 * Return pointer to next command table entry. 883 * Also return the command length, the action code and the extra string 884 * from the current entry. 885 * Return NULL if the entry is truncated. 886 */ 887 static constant unsigned char * cmd_next_entry(constant unsigned char *entry, constant unsigned char *end, mutable int *action, mutable constant unsigned char **extra, mutable size_t *cmdlen) 888 { 889 int a; 890 constant unsigned char *oentry = entry; 891 entry = skip_to_null(entry, end); /* skip to end of cmd */ 892 if (entry == NULL) 893 return NULL; 894 if (cmdlen != NULL) 895 *cmdlen = ptr_diff(entry, oentry); 896 do { /* skip any A_SKIP bytes between cmd and action */ 897 if (++entry >= end) 898 return NULL; 899 a = *entry; 900 } while (a == A_SKIP); 901 ++entry; /* skip action */ 902 if (extra != NULL) 903 *extra = (a & A_EXTRA) ? entry : NULL; 904 if (a & A_EXTRA) 905 { 906 entry = skip_to_null(entry, end); /* skip to end of extra string */ 907 if (entry == NULL) 908 return NULL; 909 ++entry; /* skip null at end of extra string */ 910 a &= ~A_EXTRA; 911 } 912 if (action != NULL) 913 *action = a; 914 return entry; 915 } 916 917 #if USERFILE 918 /* 919 * Does a command table contain a #stop directive? 920 */ 921 static lbool table_has_stop(struct tablelist *t) 922 { 923 constant unsigned char *entry = t->t_start; 924 925 while (entry < t->t_end) 926 { 927 int action; 928 entry = cmd_next_entry(entry, t->t_end, &action, NULL, NULL); 929 if (entry == NULL) 930 break; 931 if (action == A_END_LIST) 932 return TRUE; 933 } 934 return FALSE; 935 } 936 937 /* 938 * Find the first command table with a #stop directive, if any. 939 */ 940 static struct tablelist * find_stop_table(struct tablelist *t) 941 { 942 for (; t != NULL; t = t->t_next) 943 { 944 if (table_has_stop(t)) 945 return t; 946 } 947 return NULL; 948 } 949 #endif /* USERFILE */ 950 951 /* 952 * Search a list of command tables for the command string in cmd. 953 */ 954 static int cmd_decode(struct tablelist *tlist, constant char *cmd, lbool anchored, constant char **extra) 955 { 956 int action = A_INVALID; 957 size_t match_len = 0; 958 constant unsigned char *table = NULL; 959 constant unsigned char *endtable; 960 961 if (extra != NULL) 962 *extra = NULL; 963 while (tlist != NULL) 964 { 965 int taction; 966 constant unsigned char *textra; 967 size_t tcmdlen; 968 size_t tmatch; 969 if (table == NULL) 970 { 971 /* Beginning of table: set start/end pointers. */ 972 table = tlist->t_start; 973 endtable = tlist->t_end; 974 } 975 if (anchored) 976 tmatch = (strcmp((constant char *) table, cmd) == 0) ? strlen(cmd) : 0; 977 else 978 tmatch = cmd_match((constant char *) table, cmd); 979 table = cmd_next_entry(table, tlist->t_end, &taction, &textra, &tcmdlen); 980 if (table == NULL) 981 { 982 /* Truncated/malformed entry; ignore this entry and move to next table. */ 983 tlist = tlist->t_next; 984 continue; 985 } 986 if (table >= endtable) 987 { 988 /* Last entry in table; process this entry then move to next table. */ 989 tlist = tlist->t_next; 990 table = NULL; 991 } 992 if (taction == A_END_LIST) 993 break; 994 if (tmatch >= match_len) 995 { 996 if (tmatch == tcmdlen) 997 { 998 /* (Last chars of) cmd matches this table entry. */ 999 action = taction; 1000 if (extra != NULL) 1001 *extra = (constant char *) textra; 1002 } else if (tmatch > 0 && (tmatch > match_len || !anchored)) 1003 { 1004 /* cmd is a prefix of this table entry */ 1005 action = A_PREFIX; 1006 if (extra != NULL) 1007 *extra = NULL; 1008 } 1009 match_len = tmatch; 1010 } 1011 } 1012 if (action == A_X11MOUSE_IN) 1013 action = x11mouse_action(FALSE); 1014 else if (action == A_X116MOUSE_IN) 1015 action = x116mouse_action(FALSE); 1016 return action; 1017 } 1018 1019 /* 1020 * Decode a command from the cmdtables list. 1021 */ 1022 public int fcmd_decode(constant char *cmd, constant char **sp) 1023 { 1024 return (cmd_decode(list_fcmd_tables, cmd, FALSE, sp)); 1025 } 1026 1027 /* 1028 * Decode a command from the edittables list. 1029 */ 1030 static int ecmd_decode(constant char *cmd, constant char **sp) 1031 { 1032 return (cmd_decode(list_ecmd_tables, cmd, FALSE, sp)); 1033 } 1034 1035 /* 1036 * Parse a comma-separated list. 1037 * Call func repeatedly, passing each item in the list. 1038 * Stop and return FALSE if func ever returns FALSE, 1039 * otherwise parse the entire list and return TRUE. 1040 */ 1041 public lbool parse_csl(lbool (*func)(constant char *word, size_t wlen, void *arg), constant char *str, void *arg) 1042 { 1043 for (;;) 1044 { 1045 constant char *estr; 1046 while (*str == ' ' || *str == ',') ++str; /* skip leading spaces/commas */ 1047 if (*str == '\0') break; 1048 estr = strchr(str, ','); 1049 if (estr == NULL) estr = str + strlen(str); 1050 while (estr > str && estr[-1] == ' ') --estr; /* trim trailing spaces */ 1051 if (!(*func)(str, ptr_diff(estr, str), arg)) 1052 return FALSE; 1053 str = estr; 1054 } 1055 return TRUE; 1056 } 1057 1058 /* 1059 * Display error message for parse_csl_bitmap. 1060 */ 1061 static int csl_bitmap_error(constant char *pfx, constant char *type, size_t len, constant char *name) 1062 { 1063 PARG parg; 1064 size_t msglen = len + strlen(pfx) + strlen(type) + 32; 1065 char *msg = ecalloc(msglen, sizeof(char)); 1066 SNPRINTF4(msg, msglen, "%s: %s name \"%.*s\"", pfx, type, (int) len, name); 1067 parg.p_string = msg; 1068 error("%s", &parg); 1069 free(msg); 1070 return 0; 1071 } 1072 1073 /* 1074 * Return the bit value of a csl_bitmap name. 1075 */ 1076 static int csl_bitmap_bit(constant char *name, size_t len, struct csl_bitmap_def *defs, int num_defs, constant char *pfx) 1077 { 1078 int i; 1079 int match = -1; 1080 1081 for (i = 0; i < num_defs; i++) 1082 { 1083 if (strncmp(defs[i].bit_name, name, len) == 0) 1084 { 1085 if (match >= 0) /* name is ambiguous */ 1086 return csl_bitmap_error(pfx, LM(ambiguous), len, name); 1087 match = i; 1088 } 1089 } 1090 if (match < 0) 1091 return csl_bitmap_error(pfx, LM(invalid), len, name); 1092 return defs[match].bit_value; 1093 } 1094 1095 /* State for parse_csl_bitmap. */ 1096 struct csl_bitmap_info 1097 { 1098 int bitmap; /* bitmap being constructed */ 1099 struct csl_bitmap_def *def; /* array of name/value pairs */ 1100 int num_defs; /* length of def array */ 1101 constant char *pfx; /* prefix for error messages */ 1102 }; 1103 1104 /* 1105 * Set a bit in a csl_bitmap_info based on a csl_bitmap name. 1106 */ 1107 static lbool csl_bitmap_set(constant char *word, size_t wlen, void *arg) 1108 { 1109 struct csl_bitmap_info *info = (struct csl_bitmap_info *) arg; 1110 info->bitmap |= csl_bitmap_bit(word, wlen, info->def, info->num_defs, info->pfx); 1111 return TRUE; 1112 } 1113 1114 /* 1115 * Parse a comma-separated list of csl_bitmap names and return 1116 * the combined bitmap value. 1117 */ 1118 public int parse_csl_bitmap(constant char *str, struct csl_bitmap_def *defs, int num_defs, constant char *pfx) 1119 { 1120 struct csl_bitmap_info info; 1121 info.bitmap = 0; 1122 info.def = defs; 1123 info.num_defs = num_defs; 1124 info.pfx = pfx; 1125 if (!parse_csl(csl_bitmap_set, str, &info)) 1126 return -1; 1127 return info.bitmap; 1128 } 1129 1130 /* 1131 * Should we ignore the setting of an environment variable? 1132 */ 1133 static lbool word_no_match(constant char *word, size_t wlen, void *arg) 1134 { 1135 constant char *var = (constant char *) arg; 1136 return !(wlen == strlen(var) && strncmp(var, word, wlen) == 0); 1137 } 1138 static lbool ignore_env(constant char *var) 1139 { 1140 if (isnullenv(no_config)) 1141 return FALSE; /* no_config is not set; don't ignore anything */ 1142 /* no_config is set; ignore any var that does not appear in no_config */ 1143 return parse_csl(word_no_match, no_config, (void*) var); 1144 } 1145 1146 #if !GETENV_NONVOLATILE 1147 1148 /* 1149 * Cache for environment variables. 1150 * We keep them in a cache to ensure that lgetenv always returns 1151 * a string with indefinite lifetime. POSIX specifies that a string 1152 * returned from getenv may be overwritten by a subsequent getenv. 1153 */ 1154 struct env_cache { 1155 struct env_cache *ec_next; 1156 char *ec_var; 1157 char *ec_value; 1158 }; 1159 1160 static struct env_cache *env_cache_list = NULL; 1161 1162 /* 1163 * Add an environment variable to the cache. 1164 */ 1165 static void addenv_cache(constant char *var, constant char *value) 1166 { 1167 struct env_cache *ec = ecalloc(1, sizeof(struct env_cache)); 1168 1169 ec->ec_var = save(var); 1170 ec->ec_value = save(value); 1171 ec->ec_next = env_cache_list; 1172 env_cache_list = ec; 1173 } 1174 1175 /* 1176 * Find an environment variable in the cache. 1177 */ 1178 static constant char * getenv_cache(constant char *var) 1179 { 1180 struct env_cache *ec; 1181 1182 for (ec = env_cache_list; ec != NULL; ec = ec->ec_next) 1183 { 1184 if (strcmp(var, ec->ec_var) == 0) 1185 return ec->ec_value; 1186 } 1187 return NULL; 1188 } 1189 1190 #endif /* GETENV_NONVOLATILE */ 1191 1192 /* 1193 * Get the value of an environment variable. 1194 * Looks first in the lesskey file, then in the real environment. 1195 */ 1196 public constant char * lgetenv(constant char *var) 1197 { 1198 int a; 1199 constant char *s; 1200 1201 if (ignore_env(var)) 1202 return (NULL); 1203 a = cmd_decode(list_var_tables, var, TRUE, &s); 1204 if (a == EV_OK) 1205 return (s); 1206 #if !GETENV_NONVOLATILE 1207 s = getenv_cache(var); 1208 if (!isnullenv(s)) 1209 return (s); 1210 #endif 1211 s = getenv(var); 1212 if (!isnullenv(s)) 1213 { 1214 #if !GETENV_NONVOLATILE 1215 addenv_cache(var, s); 1216 #endif 1217 return (s); 1218 } 1219 a = cmd_decode(list_sysvar_tables, var, TRUE, &s); 1220 if (a == EV_OK) 1221 return (s); 1222 return (NULL); 1223 } 1224 1225 /* 1226 * Like lgetenv, but also uses a buffer partially filled with an env table. 1227 */ 1228 public constant char * lgetenv_ext(constant char *var, unsigned char *env_buf, size_t env_buf_len) 1229 { 1230 constant char *r; 1231 size_t e; 1232 size_t env_end = 0; 1233 1234 for (e = 0;;) 1235 { 1236 for (; e < env_buf_len; e++) 1237 if (env_buf[e] == '\0') 1238 break; 1239 if (e+1 >= env_buf_len) break; 1240 if (env_buf[++e] & A_EXTRA) 1241 { 1242 for (e = e+1; e < env_buf_len; e++) 1243 if (env_buf[e] == '\0') 1244 break; 1245 } 1246 e++; 1247 if (e >= env_buf_len) break; 1248 env_end = e; 1249 } 1250 /* Temporarily add env_buf to var_tables, do the lookup, then remove it. */ 1251 add_cmd_table(&list_var_tables, env_buf, env_end); 1252 r = lgetenv(var); 1253 pop_cmd_table(&list_var_tables); 1254 return r; 1255 } 1256 1257 /* 1258 * Is a string null or empty? 1259 */ 1260 public lbool isnullenv(constant char *s) 1261 { 1262 return (s == NULL || *s == '\0'); 1263 } 1264 1265 #if USERFILE 1266 /* 1267 * Get an "integer" from a lesskey file. 1268 * Integers are stored in a funny format: 1269 * two bytes, low order first, in radix KRADIX. 1270 */ 1271 static size_t gint(unsigned char **sp) 1272 { 1273 size_t n; 1274 1275 n = *(*sp)++; 1276 n += *(*sp)++ * KRADIX; 1277 return (n); 1278 } 1279 1280 /* 1281 * Process a new (post-v241) lesskey file. 1282 */ 1283 static int new_lesskey(unsigned char *buf, size_t len, lbool sysvar) 1284 { 1285 unsigned char *p; 1286 unsigned char *end; 1287 int c; 1288 size_t n; 1289 1290 /* 1291 * New-style lesskey file. 1292 * Extract the pieces. 1293 */ 1294 if (buf[len-3] != C0_END_LESSKEY_MAGIC || 1295 buf[len-2] != C1_END_LESSKEY_MAGIC || 1296 buf[len-1] != C2_END_LESSKEY_MAGIC) 1297 return (-1); 1298 p = buf + 4; 1299 end = buf + len; 1300 for (;;) 1301 { 1302 c = *p++; 1303 switch (c) 1304 { 1305 case CMD_SECTION: 1306 n = gint(&p); 1307 if (p+n >= end) 1308 return (-1); 1309 add_fcmd_table(p, n); 1310 p += n; 1311 break; 1312 case EDIT_SECTION: 1313 n = gint(&p); 1314 if (p+n >= end) 1315 return (-1); 1316 add_ecmd_table(p, n); 1317 p += n; 1318 break; 1319 case VAR_SECTION: 1320 n = gint(&p); 1321 if (p+n >= end) 1322 return (-1); 1323 if (sysvar) 1324 add_sysvar_table(p, n); 1325 else 1326 add_uvar_table(p, n); 1327 p += n; 1328 break; 1329 case END_SECTION: 1330 return (0); 1331 default: 1332 /* 1333 * Unrecognized section type. 1334 */ 1335 return (-1); 1336 } 1337 } 1338 } 1339 1340 /* 1341 * Set up a user command table, based on a "lesskey" file. 1342 */ 1343 public int lesskey(constant char *filename, lbool sysvar) 1344 { 1345 unsigned char *buf; 1346 POSITION len; 1347 ssize_t n; 1348 int f; 1349 1350 if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config)) 1351 return (1); 1352 /* 1353 * Try to open the lesskey file. 1354 */ 1355 f = open(filename, OPEN_READ); 1356 if (f < 0) 1357 return (1); 1358 1359 /* 1360 * Read the file into a buffer. 1361 * We first figure out the size of the file and allocate space for it. 1362 * {{ Minimal error checking is done here. 1363 * A garbage .less file will produce strange results. 1364 * To avoid a large amount of error checking code here, we 1365 * rely on the lesskey program to generate a good .less file. }} 1366 */ 1367 len = filesize(f); 1368 if (len == NULL_POSITION || len < 3) 1369 { 1370 /* 1371 * Bad file (valid file must have at least 3 chars). 1372 */ 1373 close(f); 1374 return (-1); 1375 } 1376 if ((buf = (unsigned char *) calloc((size_t)len, sizeof(char))) == NULL) 1377 { 1378 close(f); 1379 return (-1); 1380 } 1381 if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK) 1382 { 1383 free(buf); 1384 close(f); 1385 return (-1); 1386 } 1387 n = read(f, buf, (size_t) len); 1388 close(f); 1389 if (n != len) 1390 { 1391 free(buf); 1392 return (-1); 1393 } 1394 1395 /* 1396 * Verify lesskey file header. 1397 */ 1398 if (len < 4 || 1399 buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC || 1400 buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC) 1401 return (-1); 1402 return (new_lesskey(buf, (size_t) len, sysvar)); 1403 } 1404 1405 #if HAVE_LESSKEYSRC 1406 static int lesskey_text(constant char *filename, lbool sysvar, lbool content) 1407 { 1408 int r; 1409 static struct lesskey_tables tables; 1410 1411 if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config)) 1412 return (1); 1413 r = content ? parse_lesskey_content(filename, &tables) : parse_lesskey(filename, &tables); 1414 if (r != 0) 1415 return (r); 1416 add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end); 1417 add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end); 1418 if (sysvar) 1419 add_sysvar_table(tables.vartable.buf.data, tables.vartable.buf.end); 1420 else 1421 add_uvar_table(tables.vartable.buf.data, tables.vartable.buf.end); 1422 return (0); 1423 } 1424 1425 public int lesskey_src(constant char *filename, lbool sysvar) 1426 { 1427 return lesskey_text(filename, sysvar, FALSE); 1428 } 1429 1430 public int lesskey_content(constant char *content, lbool sysvar) 1431 { 1432 return lesskey_text(content, sysvar, TRUE); 1433 } 1434 1435 public void lesskey_parse_error(constant char *s) 1436 { 1437 PARG parg; 1438 parg.p_string = s; 1439 error("%s", &parg); 1440 } 1441 #endif /* HAVE_LESSKEYSRC */ 1442 1443 /* 1444 * Add a lesskey file. 1445 */ 1446 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar) 1447 { 1448 char *filename = NULL; 1449 constant char *efilename; 1450 int r; 1451 1452 #if LESSTEST 1453 if (is_lesstest()) /* Don't use lesskey files in lesstest */ 1454 return -1; 1455 #endif 1456 if (envname != NULL && (efilename = lgetenv(envname)) != NULL) 1457 filename = save(efilename); 1458 else if (sysvar) /* def_filename is full path */ 1459 filename = save(def_filename); 1460 else /* def_filename is just basename */ 1461 { 1462 /* Remove first char (normally a dot) unless stored in $HOME. */ 1463 constant char *xdg = lgetenv("XDG_CONFIG_HOME"); 1464 if (!isnullenv(xdg)) 1465 filename = dirfile(xdg, &def_filename[1], TRUE); 1466 if (filename == NULL) 1467 { 1468 constant char *home = lgetenv("HOME"); 1469 if (!isnullenv(home)) 1470 { 1471 char *cfg_dir = dirfile(home, ".config", FALSE); 1472 filename = dirfile(cfg_dir, &def_filename[1], TRUE); 1473 free(cfg_dir); 1474 } 1475 } 1476 if (filename == NULL) 1477 filename = homefile(def_filename); 1478 } 1479 if (filename == NULL) 1480 return -1; 1481 r = (*call_lesskey)(filename, sysvar); 1482 free(filename); 1483 return (r); 1484 } 1485 1486 /* 1487 * Add the content of a lesskey source file. 1488 */ 1489 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar) 1490 { 1491 constant char *content; 1492 1493 (void) call_lesskey; /* not used */ 1494 content = lgetenv(envname); 1495 if (isnullenv(content)) 1496 return; 1497 lesskey_content(content, sysvar); 1498 } 1499 #endif /* USERFILE */ 1500 1501 /* 1502 * See if a char is a special line-editing command. 1503 */ 1504 public int editchar(char c, int flags) 1505 { 1506 int action; 1507 int nch; 1508 constant char *s; 1509 char usercmd[MAX_CMDLEN+1]; 1510 1511 /* 1512 * An editing character could actually be a sequence of characters; 1513 * for example, an escape sequence sent by pressing the uparrow key. 1514 * To match the editing string, we use the command decoder 1515 * but give it the edit-commands command table 1516 * This table is constructed to match the user's keyboard. 1517 */ 1518 if (c == erase_char || c == erase2_char) 1519 return (EC_BACKSPACE); 1520 if (c == kill_char) 1521 { 1522 #if MSDOS_COMPILER==WIN32C 1523 if (!win32_kbhit()) 1524 #endif 1525 return (EC_LINEKILL); 1526 } 1527 1528 /* 1529 * Collect characters in a buffer. 1530 * Start with the one we have, and get more if we need them. 1531 */ 1532 nch = 0; 1533 do { 1534 if (nch > 0) 1535 c = getcc(); 1536 usercmd[nch] = c; 1537 usercmd[nch+1] = '\0'; 1538 nch++; 1539 action = ecmd_decode(usercmd, &s); 1540 } while (action == A_PREFIX && nch < MAX_CMDLEN); 1541 1542 if (action == EC_X11MOUSE) 1543 return (x11mouse_action(TRUE)); 1544 if (action == EC_X116MOUSE) 1545 return (x116mouse_action(TRUE)); 1546 1547 if (flags & ECF_NORIGHTLEFT) 1548 { 1549 switch (action) 1550 { 1551 case EC_RIGHT: 1552 case EC_LEFT: 1553 action = A_INVALID; 1554 break; 1555 } 1556 } 1557 #if CMD_HISTORY 1558 if (flags & ECF_NOHISTORY) 1559 { 1560 /* 1561 * The caller says there is no history list. 1562 * Reject any history-manipulation action. 1563 */ 1564 switch (action) 1565 { 1566 case EC_UP: 1567 case EC_DOWN: 1568 action = A_INVALID; 1569 break; 1570 } 1571 } 1572 #endif 1573 if (flags & ECF_NOCOMPLETE) 1574 { 1575 /* 1576 * The caller says we don't want any filename completion cmds. 1577 * Reject them. 1578 */ 1579 switch (action) 1580 { 1581 case EC_F_COMPLETE: 1582 case EC_B_COMPLETE: 1583 case EC_EXPAND: 1584 action = A_INVALID; 1585 break; 1586 } 1587 } 1588 if ((flags & ECF_PEEK) || action == A_INVALID) 1589 { 1590 /* 1591 * We're just peeking, or we didn't understand the command. 1592 * Unget all the characters we read in the loop above. 1593 * This does NOT include the original character that was 1594 * passed in as a parameter. 1595 */ 1596 while (nch > 1) 1597 { 1598 ungetcc(usercmd[--nch]); 1599 } 1600 } else 1601 { 1602 if (s != NULL) 1603 ungetsc(s); 1604 } 1605 return action; 1606 } 1607 1608