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