xref: /src/contrib/less/decode.c (revision e2abec625bf07c054f7ac2df2402d6c454113df8)
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 mousecap;
38 extern int sc_height;
39 extern char *no_config;
40 
41 static constant lbool allow_drag = TRUE;
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 static unsigned char dflt_vartable[] =
290 {
291 	'L','E','S','S','_','O','S','C','8','_','m','a','n', 0, EV_OK|A_EXTRA,
292 		/* echo '%o' | sed -e "s,^man\:\\([^(]*\\)( *\\([^)]*\\)\.*,-man '\\2' '\\1'," -e"t X" -e"s,\.*,-echo Invalid man link," -e"\: X" */
293 		'e','c','h','o',' ','\'','%','o','\'',' ','|',' ','s','e','d',' ','-','e',' ','"','s',',','^','m','a','n','\\',':','\\','\\','(','[','^','(',']','*','\\','\\',')','(',' ','*','\\','\\','(','[','^',')',']','*','\\','\\',')','\\','.','*',',','-','m','a','n',' ','\'','\\','\\','2','\'',' ','\'','\\','\\','1','\'',',','"',' ','-','e','"','t',' ','X','"',' ','-','e','"','s',',','\\','.','*',',','-','e','c','h','o',' ','I','n','v','a','l','i','d',' ','m','a','n',' ','l','i','n','k',',','"',' ','-','e','"','\\',':',' ','X','"',
294 		0,
295 
296 	'L','E','S','S','_','O','S','C','8','_','f','i','l','e', 0, EV_OK|A_EXTRA,
297 		/* eval `echo '%o' | sed -e "s,^file://\\([^/]*\\)\\(.*\\),_H=\\1;_P=\\2;_E=0," -e"t X" -e"s,.*,_E=1," -e": X"`; if [ "$_E" = 1 ]; then echo -echo Invalid file link; elif [ -z "$_H" -o "$_H" = localhost -o "$_H" = $HOSTNAME ]; then echo ":e $_P"; else echo -echo Cannot open remote file on "$_H"; fi */
298 		'e','v','a','l',' ','`','e','c','h','o',' ','\'','%','o','\'',' ','|',' ','s','e','d',' ','-','e',' ','"','s',',','^','f','i','l','e','\\',':','/','/','\\','\\','(','[','^','/',']','*','\\','\\',')','\\','\\','(','\\','.','*','\\','\\',')',',','_','H','=','\\','\\','1',';','_','P','=','\\','\\','2',';','_','E','=','0',',','"',' ','-','e','"','t',' ','X','"',' ','-','e','"','s',',','\\','.','*',',','_','E','=','1',',','"',' ','-','e','"','\\',':',' ','X','"','`',';',' ','i','f',' ','[',' ','"','$','_','E','"',' ','=',' ','1',' ',']',';',' ','t','h','e','n',' ','e','c','h','o',' ','-','e','c','h','o',' ','I','n','v','a','l','i','d',' ','f','i','l','e',' ','l','i','n','k',';',' ','e','l','i','f',' ','[',' ','-','z',' ','"','$','_','H','"',' ','-','o',' ','"','$','_','H','"',' ','=',' ','l','o','c','a','l','h','o','s','t',' ','-','o',' ','"','$','_','H','"',' ','=',' ','$','H','O','S','T','N','A','M','E',' ',']',';',' ','t','h','e','n',' ','e','c','h','o',' ','"','\\',':','e',' ','$','_','P','"',';',' ','e','l','s','e',' ','e','c','h','o',' ','-','e','c','h','o',' ','C','a','n','n','o','t',' ','o','p','e','n',' ','r','e','m','o','t','e',' ','f','i','l','e',' ','o','n',' ','"','$','_','H','"',';',' ','f','i',
299 		0,
300 };
301 
302 /*
303  * Structure to support a list of command tables.
304  */
305 struct tablelist
306 {
307 	struct tablelist *t_next;
308 	unsigned char *t_start;
309 	unsigned char *t_end;
310 };
311 
312 /*
313  * List of command tables and list of line-edit tables.
314  */
315 static struct tablelist *list_fcmd_tables = NULL;
316 static struct tablelist *list_ecmd_tables = NULL;
317 static struct tablelist *list_var_tables = NULL;
318 static struct tablelist *list_sysvar_tables = NULL;
319 
320 
321 /*
322  * Expand special key abbreviations in a command table.
323  */
expand_special_keys(unsigned char * table,size_t len)324 static void expand_special_keys(unsigned char *table, size_t len)
325 {
326 	unsigned char *fm;
327 	unsigned char *to;
328 	int a;
329 	constant char *repl;
330 	size_t klen;
331 
332 	for (fm = table;  fm < table + len; )
333 	{
334 		/*
335 		 * Rewrite each command in the table with any
336 		 * special key abbreviations expanded.
337 		 */
338 		for (to = fm;  *fm != '\0'; )
339 		{
340 			if (*fm != SK_SPECIAL_KEY)
341 			{
342 				*to++ = *fm++;
343 				continue;
344 			}
345 			/*
346 			 * After SK_SPECIAL_KEY, next byte is the type
347 			 * of special key (one of the SK_* constants),
348 			 * and the byte after that is the number of bytes,
349 			 * N, reserved by the abbreviation (including the
350 			 * SK_SPECIAL_KEY and key type bytes).
351 			 * Replace all N bytes with the actual bytes
352 			 * output by the special key on this terminal.
353 			 */
354 			repl = special_key_str(fm[1]);
355 			klen = fm[2] & 0377;
356 			fm += klen;
357 			if (repl == NULL || strlen(repl) > klen)
358 				repl = "\377";
359 			while (*repl != '\0')
360 				*to++ = (unsigned char) *repl++; /*{{type-issue}}*/
361 		}
362 		*to++ = '\0';
363 		/*
364 		 * Fill any unused bytes between end of command and
365 		 * the action byte with A_SKIP.
366 		 */
367 		while (to <= fm)
368 			*to++ = A_SKIP;
369 		fm++;
370 		a = *fm++ & 0377;
371 		if (a & A_EXTRA)
372 		{
373 			while (*fm++ != '\0')
374 				continue;
375 		}
376 	}
377 }
378 
379 /*
380  * Expand special key abbreviations in a list of command tables.
381  */
expand_cmd_table(struct tablelist * tlist)382 static void expand_cmd_table(struct tablelist *tlist)
383 {
384 	struct tablelist *t;
385 	for (t = tlist;  t != NULL;  t = t->t_next)
386 	{
387 		expand_special_keys(t->t_start, ptr_diff(t->t_end, t->t_start));
388 	}
389 }
390 
391 /*
392  * Expand special key abbreviations in all command tables.
393  */
expand_cmd_tables(void)394 public void expand_cmd_tables(void)
395 {
396 	expand_cmd_table(list_fcmd_tables);
397 	expand_cmd_table(list_ecmd_tables);
398 	expand_cmd_table(list_var_tables);
399 	expand_cmd_table(list_sysvar_tables);
400 }
401 
402 /*
403  * Initialize the command lists.
404  */
init_cmds(void)405 public void init_cmds(void)
406 {
407 	/*
408 	 * Add the default command tables.
409 	 */
410 	add_fcmd_table(cmdtable, sizeof(cmdtable));
411 	add_ecmd_table(edittable, sizeof(edittable));
412 	add_sysvar_table(dflt_vartable, sizeof(dflt_vartable));
413 #if USERFILE
414 #ifdef BINDIR /* For backwards compatibility */
415 	/* Try to add tables in the OLD system lesskey file. */
416 	add_hometable(lesskey, NULL, BINDIR "/.sysless", TRUE);
417 #endif
418 	/*
419 	 * Try to load lesskey source file or binary file.
420 	 * If the source file succeeds, don't load binary file.
421 	 * The binary file is likely to have been generated from
422 	 * a (possibly out of date) copy of the src file,
423 	 * so loading it is at best redundant.
424 	 */
425 	/*
426 	 * Try to add tables in system lesskey src file.
427 	 */
428 #if HAVE_LESSKEYSRC
429 	if (add_hometable(lesskey_src, "LESSKEYIN_SYSTEM", LESSKEYINFILE_SYS, TRUE) != 0)
430 #endif
431 	{
432 		/*
433 		 * Try to add the tables in the system lesskey binary file.
434 		 */
435 		add_hometable(lesskey, "LESSKEY_SYSTEM", LESSKEYFILE_SYS, TRUE);
436 	}
437 	/*
438 	 * Try to add tables in the lesskey src file "$HOME/.lesskey".
439 	 */
440 #if HAVE_LESSKEYSRC
441 	if (add_hometable(lesskey_src, "LESSKEYIN", DEF_LESSKEYINFILE, FALSE) != 0)
442 #endif
443 	{
444 		/*
445 		 * Try to add the tables in the standard lesskey binary file "$HOME/.less".
446 		 */
447 		add_hometable(lesskey, "LESSKEY", LESSKEYFILE, FALSE);
448 	}
449 
450 	add_content_table(lesskey_content, "LESSKEY_CONTENT_SYSTEM", TRUE);
451 	add_content_table(lesskey_content, "LESSKEY_CONTENT", FALSE);
452 #endif /* USERFILE */
453 }
454 
455 /*
456  * Add a command table.
457  */
add_cmd_table(struct tablelist ** tlist,unsigned char * buf,size_t len)458 static int add_cmd_table(struct tablelist **tlist, unsigned char *buf, size_t len)
459 {
460 	struct tablelist *t;
461 
462 	if (len == 0)
463 		return (0);
464 	/*
465 	 * Allocate a tablelist structure, initialize it,
466 	 * and link it into the list of tables.
467 	 */
468 	if ((t = (struct tablelist *)
469 			calloc(1, sizeof(struct tablelist))) == NULL)
470 	{
471 		return (-1);
472 	}
473 	t->t_start = buf;
474 	t->t_end = buf + len;
475 	t->t_next = NULL;
476 	if (*tlist == NULL)
477 		*tlist = t;
478 	else
479 	{
480 		struct tablelist *e;
481 		for (e = *tlist;  e->t_next != NULL;  e = e->t_next)
482 			continue;
483 		e->t_next = t;
484 	}
485 	return (0);
486 }
487 
488 /*
489  * Remove the last command table in a list.
490  */
pop_cmd_table(struct tablelist ** tlist)491 static void pop_cmd_table(struct tablelist **tlist)
492 {
493 	struct tablelist *t;
494 	if (*tlist == NULL)
495 		return;
496 	if ((*tlist)->t_next == NULL)
497 	{
498 		t = *tlist;
499 		*tlist = NULL;
500 	} else
501 	{
502 		struct tablelist *e;
503 		for (e = *tlist;  e->t_next->t_next != NULL;  e = e->t_next)
504 			continue;
505 		t = e->t_next;
506 		e->t_next = NULL;
507 	}
508 	free(t);
509 }
510 
511 /*
512  * Add a command table.
513  */
add_fcmd_table(unsigned char * buf,size_t len)514 public void add_fcmd_table(unsigned char *buf, size_t len)
515 {
516 	if (add_cmd_table(&list_fcmd_tables, buf, len) < 0)
517 		error("Warning: some commands disabled", NULL_PARG);
518 }
519 
520 /*
521  * Add an editing command table.
522  */
add_ecmd_table(unsigned char * buf,size_t len)523 public void add_ecmd_table(unsigned char *buf, size_t len)
524 {
525 	if (add_cmd_table(&list_ecmd_tables, buf, len) < 0)
526 		error("Warning: some edit commands disabled", NULL_PARG);
527 }
528 
529 /*
530  * Add an environment variable table.
531  */
add_var_table(struct tablelist ** tlist,mutable unsigned char * buf,size_t len)532 static void add_var_table(struct tablelist **tlist, mutable unsigned char *buf, size_t len)
533 {
534 	struct xbuffer xbuf;
535 
536 	xbuf_init(&xbuf);
537 	expand_evars((mutable char*)buf, len, &xbuf); /*{{unsigned-issue}}*/
538 	/* {{ We leak the table in buf. expand_evars scribbled in it so it's useless anyway. }} */
539 	if (add_cmd_table(tlist, xbuf.data, xbuf.end) < 0)
540 		error("Warning: environment variables from lesskey file unavailable", NULL_PARG);
541 }
542 
add_uvar_table(unsigned char * buf,size_t len)543 public void add_uvar_table(unsigned char *buf, size_t len)
544 {
545 	add_var_table(&list_var_tables, buf, len);
546 }
547 
add_sysvar_table(unsigned char * buf,size_t len)548 public void add_sysvar_table(unsigned char *buf, size_t len)
549 {
550 	add_var_table(&list_sysvar_tables, buf, len);
551 }
552 
553 /*
554  * Return action for a mouse wheel down event.
555  */
mouse_wheel_down(void)556 static int mouse_wheel_down(void)
557 {
558 	return ((mousecap == OPT_ONPLUS) ? A_B_MOUSE : A_F_MOUSE);
559 }
560 
561 /*
562  * Return action for a mouse wheel up event.
563  */
mouse_wheel_up(void)564 static int mouse_wheel_up(void)
565 {
566 	return ((mousecap == OPT_ONPLUS) ? A_F_MOUSE : A_B_MOUSE);
567 }
568 
569 /*
570  * Return action for the left mouse button trigger.
571  */
mouse_button_left(int x,int y,lbool down,lbool drag)572 static int mouse_button_left(int x, int y, lbool down, lbool drag)
573 {
574 	static int last_drag_y = -1;
575 	static int last_click_y = -1;
576 
577 	if (down && !drag)
578 	{
579 		last_drag_y = last_click_y = y;
580 	}
581 	if (allow_drag && drag && last_drag_y >= 0)
582 	{
583 		/* Drag text up/down */
584 		if (y > last_drag_y)
585 		{
586 			cmd_exec();
587 			backward(y - last_drag_y, FALSE, FALSE, FALSE);
588 			last_drag_y = y;
589 		} else if (y < last_drag_y)
590 		{
591 			cmd_exec();
592 			forward(last_drag_y - y, FALSE, FALSE, FALSE);
593 			last_drag_y = y;
594 		}
595 	} else if (!down)
596 	{
597 #if OSC8_LINK
598 		if (secure_allow(SF_OSC8_OPEN))
599 		{
600 			if (osc8_click(y, x))
601 				return (A_NOACTION);
602 		}
603 #else
604 		(void) x;
605 #endif /* OSC8_LINK */
606 		if (y < sc_height-1 && y == last_click_y)
607 		{
608 			setmark('#', y);
609 			screen_trashed();
610 		}
611 	}
612 	return (A_NOACTION);
613 }
614 
615 /*
616  * Return action for the right mouse button trigger.
617  */
mouse_button_right(int x,int y,lbool down,lbool drag)618 static int mouse_button_right(int x, int y, lbool down, lbool drag)
619 {
620 	(void) x; (void) drag;
621 	/*
622 	 * {{ unlike mouse_button_left, we could return an action,
623 	 *    but keep it near mouse_button_left for readability. }}
624 	 */
625 	if (!down && y < sc_height-1)
626 	{
627 		gomark('#');
628 		screen_trashed();
629 	}
630 	return (A_NOACTION);
631 }
632 
633 /*
634  * Read a decimal integer. Return the integer and set *pterm to the terminating char.
635  */
getcc_int(char * pterm)636 static int getcc_int(char *pterm)
637 {
638 	int num = 0;
639 	int digits = 0;
640 	for (;;)
641 	{
642 		char ch = getcc();
643 		if (ch < '0' || ch > '9')
644 		{
645 			if (pterm != NULL) *pterm = ch;
646 			if (digits == 0)
647 				return (-1);
648 			return (num);
649 		}
650 		if (ckd_mul(&num, num, 10) || ckd_add(&num, num, ch - '0'))
651 			return -1;
652 		++digits;
653 	}
654 }
655 
x11mouse_button(int btn,int x,int y,lbool down,lbool drag)656 static int x11mouse_button(int btn, int x, int y, lbool down, lbool drag)
657 {
658 	switch (btn) {
659 	case X11MOUSE_BUTTON1:
660 		return mouse_button_left(x, y, down, drag);
661 	/* is BUTTON2 the rightmost with 2-buttons mouse? */
662 	case X11MOUSE_BUTTON2:
663 	case X11MOUSE_BUTTON3:
664 		return mouse_button_right(x, y, down, drag);
665 	}
666 	return (A_NOACTION);
667 }
668 
669 /*
670  * Read suffix of mouse input and return the action to take.
671  * The prefix ("\e[M") has already been read.
672  */
x11mouse_action(lbool skip)673 static int x11mouse_action(lbool skip)
674 {
675 	static int prev_b = X11MOUSE_BUTTON_REL;
676 	int x, y;
677 	int b = getcc() - X11MOUSE_OFFSET;
678 	lbool drag = ((b & X11MOUSE_DRAG) != 0);
679 	b &= ~X11MOUSE_DRAG;
680 	x = getcc() - X11MOUSE_OFFSET-1;
681 	y = getcc() - X11MOUSE_OFFSET-1;
682 	if (skip)
683 		return (A_NOACTION);
684 	switch (b) {
685 	case X11MOUSE_WHEEL_DOWN:
686 		return mouse_wheel_down();
687 	case X11MOUSE_WHEEL_UP:
688 		return mouse_wheel_up();
689 	case X11MOUSE_BUTTON1:
690 	case X11MOUSE_BUTTON2:
691 	case X11MOUSE_BUTTON3:
692 		prev_b = b;
693 		return x11mouse_button(b, x, y, TRUE, drag);
694 	case X11MOUSE_BUTTON_REL: /* button up */
695 		return x11mouse_button(prev_b, x, y, FALSE, drag);
696 	}
697 	return (A_NOACTION);
698 }
699 
700 /*
701  * Read suffix of mouse input and return the action to take.
702  * The prefix ("\e[<") has already been read.
703  */
x116mouse_action(lbool skip)704 static int x116mouse_action(lbool skip)
705 {
706 	char ch;
707 	int x, y;
708 	int b = getcc_int(&ch);
709 	lbool drag = ((b & X11MOUSE_DRAG) != 0);
710 	b &= ~X11MOUSE_DRAG;
711 	if (b < 0 || ch != ';') return (A_NOACTION);
712 	x = getcc_int(&ch) - 1;
713 	if (x < 0 || ch != ';') return (A_NOACTION);
714 	y = getcc_int(&ch) - 1;
715 	if (y < 0) return (A_NOACTION);
716 	if (skip)
717 		return (A_NOACTION);
718 	switch (b) {
719 	case X11MOUSE_WHEEL_DOWN:
720 		return mouse_wheel_down();
721 	case X11MOUSE_WHEEL_UP:
722 		return mouse_wheel_up();
723 	case X11MOUSE_BUTTON1:
724 	case X11MOUSE_BUTTON2:
725 	case X11MOUSE_BUTTON3: {
726 		lbool down = (ch == 'M');
727 		lbool up = (ch == 'm');
728 		if (up || down)
729 			return x11mouse_button(b, x, y, down, drag);
730 		break; }
731 	}
732 	return (A_NOACTION);
733 }
734 
735 /*
736  * Return the largest N such that the first N chars of goal
737  * are equal to the last N chars of str.
738  */
cmd_match(constant char * goal,constant char * str)739 static size_t cmd_match(constant char *goal, constant char *str)
740 {
741 	size_t slen = strlen(str);
742 	size_t len;
743 	for (len = slen;  len > 0;  len--)
744 		if (strncmp(str + slen - len, goal, len) == 0)
745 			break;
746 	return len;
747 }
748 
749 /*
750  * Return pointer to next command table entry.
751  * Also return the action and the extra string from the entry.
752  */
cmd_next_entry(constant unsigned char * entry,mutable int * action,mutable constant unsigned char ** extra,mutable size_t * cmdlen)753 static constant unsigned char * cmd_next_entry(constant unsigned char *entry, mutable int *action, mutable constant unsigned char **extra, mutable size_t *cmdlen)
754 {
755 	int a;
756 	constant unsigned char *oentry = entry;
757 	while (*entry != '\0') /* skip cmd */
758 		++entry;
759 	if (cmdlen != NULL)
760 		*cmdlen = ptr_diff(entry, oentry);
761 	do
762 		a = *++entry; /* get action */
763 	while (a == A_SKIP);
764 	++entry; /* skip action */
765 	if (extra != NULL)
766 		*extra = (a & A_EXTRA) ? entry : NULL;
767 	if (a & A_EXTRA)
768 	{
769 		while (*entry++ != '\0') /* skip extra string */
770 			continue;
771 		a &= ~A_EXTRA;
772 	}
773 	if (action != NULL)
774 		*action = a;
775 	return entry;
776 }
777 
778 /*
779  * Search a single command table for the command string in cmd.
780  */
cmd_search(constant char * cmd,constant unsigned char * table,constant unsigned char * endtable,constant unsigned char ** extra,size_t * mlen)781 static int cmd_search(constant char *cmd, constant unsigned char *table, constant unsigned char *endtable, constant unsigned char **extra, size_t *mlen)
782 {
783 	int action = A_INVALID;
784 	size_t match_len = 0;
785 	if (extra != NULL)
786 		*extra = NULL;
787 	while (table < endtable)
788 	{
789 		int taction;
790 		constant unsigned char *textra;
791 		size_t cmdlen;
792 		size_t match = cmd_match((constant char *) table, cmd);
793 		table = cmd_next_entry(table, &taction, &textra, &cmdlen);
794 		if (taction == A_END_LIST)
795 			return (-action);
796 		if (match >= match_len)
797 		{
798 			if (match == cmdlen) /* (last chars of) cmd matches this table entry */
799 			{
800 				action = taction;
801 				if (extra != NULL)
802 					*extra = textra;
803 			} else if (match > 0 && action == A_INVALID) /* cmd is a prefix of this table entry */
804 			{
805 				action = A_PREFIX;
806 			}
807 			match_len = match;
808 		}
809 	}
810 	if (mlen != NULL)
811 		*mlen = match_len;
812 	return (action);
813 }
814 
815 /*
816  * Decode a command character and return the associated action.
817  * The "extra" string, if any, is returned in sp.
818  */
cmd_decode(struct tablelist * tlist,constant char * cmd,constant char ** sp)819 static int cmd_decode(struct tablelist *tlist, constant char *cmd, constant char **sp)
820 {
821 	struct tablelist *t;
822 	int action = A_INVALID;
823 	size_t match_len = 0;
824 
825 	/*
826 	 * Search for the cmd thru all the command tables.
827 	 * If we find it more than once, take the last one.
828 	 */
829 	*sp = NULL;
830 	for (t = tlist;  t != NULL;  t = t->t_next)
831 	{
832 		constant unsigned char *tsp;
833 		size_t mlen = match_len;
834 		int taction = cmd_search(cmd, t->t_start, t->t_end, &tsp, &mlen);
835 		if (mlen >= match_len)
836 		{
837 			match_len = mlen;
838 			if (taction != A_INVALID)
839 			{
840 				*sp = (constant char *) tsp;
841 				if (taction < 0)
842 				{
843 					action = -taction;
844 					break;
845 				}
846 				action = taction;
847 			}
848 		}
849 	}
850 	if (action == A_X11MOUSE_IN)
851 		action = x11mouse_action(FALSE);
852 	else if (action == A_X116MOUSE_IN)
853 		action = x116mouse_action(FALSE);
854 	return (action);
855 }
856 
857 /*
858  * Decode a command from the cmdtables list.
859  */
fcmd_decode(constant char * cmd,constant char ** sp)860 public int fcmd_decode(constant char *cmd, constant char **sp)
861 {
862 	return (cmd_decode(list_fcmd_tables, cmd, sp));
863 }
864 
865 /*
866  * Decode a command from the edittables list.
867  */
ecmd_decode(constant char * cmd,constant char ** sp)868 public int ecmd_decode(constant char *cmd, constant char **sp)
869 {
870 	return (cmd_decode(list_ecmd_tables, cmd, sp));
871 }
872 
873 /*
874  * Parse a comma-separated list.
875  * Call func repeatedly, passing each item in the list.
876  * Stop and return FALSE if func ever returns FALSE,
877  * otherwise parse the entire list and return TRUE.
878  */
parse_csl(lbool (* func)(constant char * word,size_t wlen,void * arg),constant char * str,void * arg)879 public lbool parse_csl(lbool (*func)(constant char *word, size_t wlen, void *arg), constant char *str, void *arg)
880 {
881 	for (;;)
882 	{
883 		constant char *estr;
884 		while (*str == ' ' || *str == ',') ++str; /* skip leading spaces/commas */
885 		if (*str == '\0') break;
886 		estr = strchr(str, ',');
887 		if (estr == NULL) estr = str + strlen(str);
888 		while (estr > str && estr[-1] == ' ') --estr; /* trim trailing spaces */
889 		if (!(*func)(str, ptr_diff(estr, str), arg))
890 			return FALSE;
891 		str = estr;
892 	}
893 	return TRUE;
894 }
895 
896 /*
897  * Should we ignore the setting of an environment variable?
898  */
word_no_match(constant char * word,size_t wlen,void * arg)899 static lbool word_no_match(constant char *word, size_t wlen, void *arg)
900 {
901 	constant char *var = (constant char *) arg;
902 	return !(wlen == strlen(var) && strncmp(var, word, wlen) == 0);
903 }
ignore_env(constant char * var)904 static lbool ignore_env(constant char *var)
905 {
906 	if (isnullenv(no_config))
907 		return FALSE; /* no_config is not set; don't ignore anything */
908 	/* no_config is set; ignore any var that does not appear in no_config */
909 	return parse_csl(word_no_match, no_config, (void*) var);
910 }
911 
912 /*
913  * Get the value of an environment variable.
914  * Looks first in the lesskey file, then in the real environment.
915  */
lgetenv(constant char * var)916 public constant char * lgetenv(constant char *var)
917 {
918 	int a;
919 	constant char *s;
920 
921 	if (ignore_env(var))
922 		return (NULL);
923 	a = cmd_decode(list_var_tables, var, &s);
924 	if (a == EV_OK)
925 		return (s);
926 	s = getenv(var);
927 	if (s != NULL && *s != '\0')
928 		return (s);
929 	a = cmd_decode(list_sysvar_tables, var, &s);
930 	if (a == EV_OK)
931 		return (s);
932 	return (NULL);
933 }
934 
935 /*
936  * Like lgetenv, but also uses a buffer partially filled with an env table.
937  */
lgetenv_ext(constant char * var,unsigned char * env_buf,size_t env_buf_len)938 public constant char * lgetenv_ext(constant char *var, unsigned char *env_buf, size_t env_buf_len)
939 {
940 	constant char *r;
941 	size_t e;
942 	size_t env_end = 0;
943 
944 	for (e = 0;;)
945 	{
946 		for (; e < env_buf_len; e++)
947 			if (env_buf[e] == '\0')
948 				break;
949 		if (e >= env_buf_len) break;
950 		if (env_buf[++e] & A_EXTRA)
951 		{
952 			for (e = e+1; e < env_buf_len; e++)
953 				if (env_buf[e] == '\0')
954 					break;
955 		}
956 		e++;
957 		if (e >= env_buf_len) break;
958 		env_end = e;
959 	}
960 	/* Temporarily add env_buf to var_tables, do the lookup, then remove it. */
961 	add_uvar_table(env_buf, env_end);
962 	r = lgetenv(var);
963 	pop_cmd_table(&list_var_tables);
964 	return r;
965 }
966 
967 /*
968  * Is a string null or empty?
969  */
isnullenv(constant char * s)970 public lbool isnullenv(constant char *s)
971 {
972 	return (s == NULL || *s == '\0');
973 }
974 
975 #if USERFILE
976 /*
977  * Get an "integer" from a lesskey file.
978  * Integers are stored in a funny format:
979  * two bytes, low order first, in radix KRADIX.
980  */
gint(unsigned char ** sp)981 static size_t gint(unsigned char **sp)
982 {
983 	size_t n;
984 
985 	n = *(*sp)++;
986 	n += *(*sp)++ * KRADIX;
987 	return (n);
988 }
989 
990 /*
991  * Process an old (pre-v241) lesskey file.
992  */
old_lesskey(unsigned char * buf,size_t len)993 static int old_lesskey(unsigned char *buf, size_t len)
994 {
995 	/*
996 	 * Old-style lesskey file.
997 	 * The file must end with either
998 	 *     ...,cmd,0,action
999 	 * or  ...,cmd,0,action|A_EXTRA,string,0
1000 	 * So the last byte or the second to last byte must be zero.
1001 	 */
1002 	if (buf[len-1] != '\0' && buf[len-2] != '\0')
1003 		return (-1);
1004 	add_fcmd_table(buf, len);
1005 	return (0);
1006 }
1007 
1008 /*
1009  * Process a new (post-v241) lesskey file.
1010  */
new_lesskey(unsigned char * buf,size_t len,lbool sysvar)1011 static int new_lesskey(unsigned char *buf, size_t len, lbool sysvar)
1012 {
1013 	unsigned char *p;
1014 	unsigned char *end;
1015 	int c;
1016 	size_t n;
1017 
1018 	/*
1019 	 * New-style lesskey file.
1020 	 * Extract the pieces.
1021 	 */
1022 	if (buf[len-3] != C0_END_LESSKEY_MAGIC ||
1023 	    buf[len-2] != C1_END_LESSKEY_MAGIC ||
1024 	    buf[len-1] != C2_END_LESSKEY_MAGIC)
1025 		return (-1);
1026 	p = buf + 4;
1027 	end = buf + len;
1028 	for (;;)
1029 	{
1030 		c = *p++;
1031 		switch (c)
1032 		{
1033 		case CMD_SECTION:
1034 			n = gint(&p);
1035 			if (p+n >= end)
1036 				return (-1);
1037 			add_fcmd_table(p, n);
1038 			p += n;
1039 			break;
1040 		case EDIT_SECTION:
1041 			n = gint(&p);
1042 			if (p+n >= end)
1043 				return (-1);
1044 			add_ecmd_table(p, n);
1045 			p += n;
1046 			break;
1047 		case VAR_SECTION:
1048 			n = gint(&p);
1049 			if (p+n >= end)
1050 				return (-1);
1051 			if (sysvar)
1052 				add_sysvar_table(p, n);
1053 			else
1054 				add_uvar_table(p, n);
1055 			p += n;
1056 			break;
1057 		case END_SECTION:
1058 			return (0);
1059 		default:
1060 			/*
1061 			 * Unrecognized section type.
1062 			 */
1063 			return (-1);
1064 		}
1065 	}
1066 }
1067 
1068 /*
1069  * Set up a user command table, based on a "lesskey" file.
1070  */
lesskey(constant char * filename,lbool sysvar)1071 public int lesskey(constant char *filename, lbool sysvar)
1072 {
1073 	unsigned char *buf;
1074 	POSITION len;
1075 	ssize_t n;
1076 	int f;
1077 
1078 	if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config))
1079 		return (1);
1080 	/*
1081 	 * Try to open the lesskey file.
1082 	 */
1083 	f = open(filename, OPEN_READ);
1084 	if (f < 0)
1085 		return (1);
1086 
1087 	/*
1088 	 * Read the file into a buffer.
1089 	 * We first figure out the size of the file and allocate space for it.
1090 	 * {{ Minimal error checking is done here.
1091 	 *    A garbage .less file will produce strange results.
1092 	 *    To avoid a large amount of error checking code here, we
1093 	 *    rely on the lesskey program to generate a good .less file. }}
1094 	 */
1095 	len = filesize(f);
1096 	if (len == NULL_POSITION || len < 3)
1097 	{
1098 		/*
1099 		 * Bad file (valid file must have at least 3 chars).
1100 		 */
1101 		close(f);
1102 		return (-1);
1103 	}
1104 	if ((buf = (unsigned char *) calloc((size_t)len, sizeof(char))) == NULL)
1105 	{
1106 		close(f);
1107 		return (-1);
1108 	}
1109 	if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
1110 	{
1111 		free(buf);
1112 		close(f);
1113 		return (-1);
1114 	}
1115 	n = read(f, buf, (size_t) len);
1116 	close(f);
1117 	if (n != len)
1118 	{
1119 		free(buf);
1120 		return (-1);
1121 	}
1122 
1123 	/*
1124 	 * Figure out if this is an old-style (before version 241)
1125 	 * or new-style lesskey file format.
1126 	 */
1127 	if (len < 4 ||
1128 	    buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC ||
1129 	    buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC)
1130 		return (old_lesskey(buf, (size_t) len));
1131 	return (new_lesskey(buf, (size_t) len, sysvar));
1132 }
1133 
1134 #if HAVE_LESSKEYSRC
lesskey_text(constant char * filename,lbool sysvar,lbool content)1135 static int lesskey_text(constant char *filename, lbool sysvar, lbool content)
1136 {
1137 	int r;
1138 	static struct lesskey_tables tables;
1139 
1140 	if (!secure_allow(SF_LESSKEY) || !isnullenv(no_config))
1141 		return (1);
1142 	r = content ? parse_lesskey_content(filename, &tables) : parse_lesskey(filename, &tables);
1143 	if (r != 0)
1144 		return (r);
1145 	add_fcmd_table(tables.cmdtable.buf.data, tables.cmdtable.buf.end);
1146 	add_ecmd_table(tables.edittable.buf.data, tables.edittable.buf.end);
1147 	if (sysvar)
1148 		add_sysvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1149 	else
1150 		add_uvar_table(tables.vartable.buf.data, tables.vartable.buf.end);
1151 	return (0);
1152 }
1153 
lesskey_src(constant char * filename,lbool sysvar)1154 public int lesskey_src(constant char *filename, lbool sysvar)
1155 {
1156 	return lesskey_text(filename, sysvar, FALSE);
1157 }
1158 
lesskey_content(constant char * content,lbool sysvar)1159 public int lesskey_content(constant char *content, lbool sysvar)
1160 {
1161 	return lesskey_text(content, sysvar, TRUE);
1162 }
1163 
lesskey_parse_error(char * s)1164 void lesskey_parse_error(char *s)
1165 {
1166 	PARG parg;
1167 	parg.p_string = s;
1168 	error("%s", &parg);
1169 }
1170 #endif /* HAVE_LESSKEYSRC */
1171 
1172 /*
1173  * Add a lesskey file.
1174  */
add_hometable(int (* call_lesskey)(constant char *,lbool),constant char * envname,constant char * def_filename,lbool sysvar)1175 static int add_hometable(int (*call_lesskey)(constant char *, lbool), constant char *envname, constant char *def_filename, lbool sysvar)
1176 {
1177 	char *filename = NULL;
1178 	constant char *efilename;
1179 	int r;
1180 
1181 #if LESSTEST
1182 	if (is_lesstest()) /* Don't use lesskey files in lesstest */
1183 		return -1;
1184 #endif
1185 	if (envname != NULL && (efilename = lgetenv(envname)) != NULL)
1186 		filename = save(efilename);
1187 	else if (sysvar) /* def_filename is full path */
1188 		filename = save(def_filename);
1189 	else /* def_filename is just basename */
1190 	{
1191 		/* Remove first char (normally a dot) unless stored in $HOME. */
1192 		constant char *xdg = lgetenv("XDG_CONFIG_HOME");
1193 		if (!isnullenv(xdg))
1194 			filename = dirfile(xdg, &def_filename[1], 1);
1195 		if (filename == NULL)
1196 		{
1197 			constant char *home = lgetenv("HOME");
1198 			if (!isnullenv(home))
1199 			{
1200 				char *cfg_dir = dirfile(home, ".config", 0);
1201 				filename = dirfile(cfg_dir, &def_filename[1], 1);
1202 				free(cfg_dir);
1203 			}
1204 		}
1205 		if (filename == NULL)
1206 			filename = homefile(def_filename);
1207 	}
1208 	if (filename == NULL)
1209 		return -1;
1210 	r = (*call_lesskey)(filename, sysvar);
1211 	free(filename);
1212 	return (r);
1213 }
1214 
1215 /*
1216  * Add the content of a lesskey source file.
1217  */
add_content_table(int (* call_lesskey)(constant char *,lbool),constant char * envname,lbool sysvar)1218 static void add_content_table(int (*call_lesskey)(constant char *, lbool), constant char *envname, lbool sysvar)
1219 {
1220 	constant char *content;
1221 
1222 	(void) call_lesskey; /* not used */
1223 	content = lgetenv(envname);
1224 	if (isnullenv(content))
1225 		return;
1226 	lesskey_content(content, sysvar);
1227 }
1228 #endif /* USERFILE */
1229 
1230 /*
1231  * See if a char is a special line-editing command.
1232  */
editchar(char c,int flags)1233 public int editchar(char c, int flags)
1234 {
1235 	int action;
1236 	int nch;
1237 	constant char *s;
1238 	char usercmd[MAX_CMDLEN+1];
1239 
1240 	/*
1241 	 * An editing character could actually be a sequence of characters;
1242 	 * for example, an escape sequence sent by pressing the uparrow key.
1243 	 * To match the editing string, we use the command decoder
1244 	 * but give it the edit-commands command table
1245 	 * This table is constructed to match the user's keyboard.
1246 	 */
1247 	if (c == erase_char || c == erase2_char)
1248 		return (EC_BACKSPACE);
1249 	if (c == kill_char)
1250 	{
1251 #if MSDOS_COMPILER==WIN32C
1252 		if (!win32_kbhit())
1253 #endif
1254 		return (EC_LINEKILL);
1255 	}
1256 
1257 	/*
1258 	 * Collect characters in a buffer.
1259 	 * Start with the one we have, and get more if we need them.
1260 	 */
1261 	nch = 0;
1262 	do {
1263 		if (nch > 0)
1264 			c = getcc();
1265 		usercmd[nch] = c;
1266 		usercmd[nch+1] = '\0';
1267 		nch++;
1268 		action = ecmd_decode(usercmd, &s);
1269 	} while (action == A_PREFIX && nch < MAX_CMDLEN);
1270 
1271 	if (action == EC_X11MOUSE)
1272 		return (x11mouse_action(TRUE));
1273 	if (action == EC_X116MOUSE)
1274 		return (x116mouse_action(TRUE));
1275 
1276 	if (flags & ECF_NORIGHTLEFT)
1277 	{
1278 		switch (action)
1279 		{
1280 		case EC_RIGHT:
1281 		case EC_LEFT:
1282 			action = A_INVALID;
1283 			break;
1284 		}
1285 	}
1286 #if CMD_HISTORY
1287 	if (flags & ECF_NOHISTORY)
1288 	{
1289 		/*
1290 		 * The caller says there is no history list.
1291 		 * Reject any history-manipulation action.
1292 		 */
1293 		switch (action)
1294 		{
1295 		case EC_UP:
1296 		case EC_DOWN:
1297 			action = A_INVALID;
1298 			break;
1299 		}
1300 	}
1301 #endif
1302 	if (flags & ECF_NOCOMPLETE)
1303 	{
1304 		/*
1305 		 * The caller says we don't want any filename completion cmds.
1306 		 * Reject them.
1307 		 */
1308 		switch (action)
1309 		{
1310 		case EC_F_COMPLETE:
1311 		case EC_B_COMPLETE:
1312 		case EC_EXPAND:
1313 			action = A_INVALID;
1314 			break;
1315 		}
1316 	}
1317 	if ((flags & ECF_PEEK) || action == A_INVALID)
1318 	{
1319 		/*
1320 		 * We're just peeking, or we didn't understand the command.
1321 		 * Unget all the characters we read in the loop above.
1322 		 * This does NOT include the original character that was
1323 		 * passed in as a parameter.
1324 		 */
1325 		while (nch > 1)
1326 		{
1327 			ungetcc(usercmd[--nch]);
1328 		}
1329 	} else
1330 	{
1331 		if (s != NULL)
1332 			ungetsc(s);
1333 	}
1334 	return action;
1335 }
1336 
1337