xref: /freebsd/contrib/less/decode.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
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