1 /* infokey.c -- read ~/.infokey
2 
3    Copyright 1999-2019 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18    Originally written by Andrew Bettison. */
19 
20 #include "info.h"
21 #include "doc.h"
22 #include "session.h"
23 #include "funs.h"
24 #include "getopt.h"
25 #include "variables.h"
26 
27 extern char *program_name;  /* in info.c */
28 
29 enum sect_e
30   {
31     info = 0,
32     ea = 1,
33     var = 2
34   };
35 
36 static void syntax_error (const char *filename, unsigned int linenum,
37 			  const char *fmt, ...) TEXINFO_PRINTFLIKE(3,4);
38 
39 /* Compilation - the real work.
40 
41 	Source file syntax
42 	------------------
43 	The source file is a line-based text file with the following
44 	structure:
45 
46 		# comments
47 		# more comments
48 
49 		#info
50 		u	prev-line
51 		d	next-line
52 		^a	invalid		# just beep
53 		\ku	prev-line
54 		#stop
55 		\kd	next-line
56 		q	quit		# of course!
57 
58 		#echo-area
59 		^a	echo-area-beg-of-line
60 		^e	echo-area-end-of-line
61 		\kr	echo-area-forward
62 		\kl	echo-area-backward
63 		\kh	echo-area-beg-of-line
64 		\ke	echo-area-end-of-line
65 
66 		#var
67 		scroll-step=1
68 		ISO-Latin=Off
69 
70 	Lines starting with '#' are comments, and are ignored.  Blank
71 	lines are ignored.  Each section is introduced by one of the
72 	following lines:
73 
74 		#info
75 		#echo-area
76 		#var
77 
78 	The sections may occur in any order.  Each section may be
79 	omitted completely.  If the 'info' section is the first in the
80 	file, its '#info' line may be omitted.
81 
82 	The 'info' and 'echo-area' sections
83 	-----------------------------------
84 	Each line in the 'info' or 'echo-area' sections has the
85 	following syntax:
86 
87 		key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
88 
89 	Where SPACE is one or more white space characters excluding
90 	newline, "action-name" is the name of a GNU Info command,
91 	"comment" is any sequence of characters excluding newline, and
92 	"key-sequence" is a concatenation of one or more key definitions
93 	using the following syntax:
94 
95 	   1.	A carat ^ followed by one character indicates a single
96 	   	control character;
97 
98 	   2.	A backslash \ followed by one, two, or three octal
99 		digits indicates a single character having that ASCII
100 		code;
101 
102 	   3.	\n indicates a single NEWLINE;
103 		\e indicates a single ESC;
104 		\r indicates a single CR;
105 		\t indicates a single TAB;
106 		\b indicates a single BACKSPACE;
107 
108 	   4.	\ku indicates the Up Arrow key;
109 	   	\kd indicates the Down Arrow key;
110 	   	\kl indicates the Left Arrow key;
111 	   	\kr indicates the Right Arrow key;
112 	   	\kP indicates the Page Up (PRIOR) key;
113 	   	\kN indicates the Page Down (NEXT) key;
114 	   	\kh indicates the Home key;
115 	   	\ke indicates the End key;
116 	   	\kx indicates the DEL key;
117 		\k followed by any other character indicates a single
118 		control-K, and the following character is interpreted
119 		as in rules 1, 2, 3, 5 and 6.
120 
121 	   5.	\m followed by any sequence defined in rules 1, 2, 3, 4
122 		or 6 indicates the "Meta" modification of that key.
123 
124 	   6.	A backslash \ followed by any character not described
125 	   	above indicates that character itself.  In particular:
126 		\\ indicates a single backslash \,
127 		\  (backslash-space) indicates a single space,
128 		\^ indicates a single caret ^,
129 
130 	If the following line:
131 
132 		#stop
133 
134 	occurs anywhere in an 'info' or 'echo-area' section, that
135 	indicates to GNU Info to suppress all of its default key
136 	bindings in that context.
137 
138 	The 'var' section
139 	-----------------
140 	Each line in the 'var' section has the following syntax:
141 
142 		variable-name = value \n
143 
144 	Where "variable-name" is the name of a GNU Info variable and
145 	"value" is the value that GNU Info will assign to that variable
146 	when commencing execution.  There must be no white space in the
147 	variable name, nor between the variable name and the '='.  All
148 	characters immediately following the '=', up to but not
149 	including the terminating newline, are considered to be the
150 	value that will be assigned.  In other words, white space
151 	following the '=' is not ignored.
152  */
153 
154 static int lookup_action (const char *actname);
155 
156 /* Read the init file.  Return true if no error was encountered.  Set
157    SUPPRESS_INFO or SUPPRESS_EA to true if the init file specified to ignore
158    default key bindings. */
159 int
compile(FILE * fp,const char * filename,int * suppress_info,int * suppress_ea)160 compile (FILE *fp, const char *filename, int *suppress_info, int *suppress_ea)
161 {
162   int error = 0; /* Set if there was a fatal error in reading init file. */
163   char rescan = 0; /* Whether to reuse the same character when moving onto the
164                       next state. */
165   unsigned int lnum = 0;
166   int c = 0;
167 
168   /* This parser is a true state machine, with no sneaky fetching
169      of input characters inside the main loop.  In other words, all
170      state is fully represented by the following variables:
171    */
172   enum
173     {
174       start_of_line,
175       start_of_comment,
176       in_line_comment,
177       in_trailing_comment,
178       get_keyseq,
179       got_keyseq,
180       get_action,
181       got_action,
182       get_varname,
183       got_varname,
184       get_equals,
185       got_equals,
186       get_value
187     }
188   state = start_of_line;
189   enum sect_e section = info;
190   enum
191     {
192       normal,
193       slosh,
194       control,
195       octal,
196       special_key
197     }
198   seqstate = normal;	/* used if state == get_keyseq */
199   char meta = 0;
200   char ocnt = 0;	/* used if state == get_keyseq && seqstate == octal */
201 
202   /* Data is accumulated in the following variables.  The code
203      avoids overflowing these strings, and throws an error
204      where appropriate if a string limit is exceeded.  These string
205      lengths are arbitrary (and should be large enough) and their
206      lengths are not hard-coded anywhere else, so increasing them
207      here will not break anything.  */
208   char oval = 0;
209   char comment[10];
210   unsigned int clen = 0;
211   int seq[20];
212   unsigned int slen = 0;
213   char act[80];
214   unsigned int alen = 0;
215   char varn[80];
216   unsigned int varlen = 0;
217   char val[80];
218   unsigned int vallen = 0;
219 
220 #define	To_seq(c) \
221 		  do { \
222 		    if (slen < sizeof seq/sizeof(int)) \
223 		      seq[slen++] = meta ? KEYMAP_META(c) : (c); \
224 		    else \
225 		      { \
226 			syntax_error(filename, lnum, \
227 				     _("key sequence too long")); \
228 			error = 1; \
229 		      } \
230 		    meta = 0; \
231 		  } while (0)
232 
233   while (!error && (rescan || (c = fgetc (fp)) != EOF))
234     {
235       rescan = 0;
236       switch (state)
237 	{
238 	case start_of_line:
239 	  lnum++;
240 	  if (c == '#')
241 	    state = start_of_comment;
242 	  else if (c != '\n')
243 	    {
244 	      switch (section)
245 		{
246 		case info:
247 		case ea:
248 		  state = get_keyseq;
249 		  seqstate = normal;
250 		  slen = 0;
251 		  break;
252 		case var:
253 		  state = get_varname;
254 		  varlen = 0;
255 		  break;
256 		}
257 	      rescan = 1;
258 	    }
259 	  break;
260 
261 	case start_of_comment:
262 	  clen = 0;
263 	  state = in_line_comment;
264 	  /* fall through */
265 	case in_line_comment:
266 	  if (c == '\n')
267 	    {
268 	      state = start_of_line;
269 	      comment[clen] = '\0';
270 	      if (strcmp (comment, "info") == 0)
271 		section = info;
272 	      else if (strcmp (comment, "echo-area") == 0)
273 		section = ea;
274 	      else if (strcmp (comment, "var") == 0)
275 		section = var;
276 	      else if (strcmp (comment, "stop") == 0
277 		       && (section == info || section == ea))
278                 {
279                   if (section == info)
280                     *suppress_info = 1;
281                   else
282                     *suppress_ea = 1;
283                 }
284 	    }
285 	  else if (clen < sizeof comment - 1)
286 	    comment[clen++] = c;
287 	  break;
288 
289 	case in_trailing_comment:
290 	  if (c == '\n')
291 	    state = start_of_line;
292 	  break;
293 
294 	case get_keyseq:
295 	  switch (seqstate)
296 	    {
297 	    case normal:
298 	      if (c == '\n' || isspace (c))
299 		{
300 		  state = got_keyseq;
301 		  rescan = 1;
302 		  if (slen == 0)
303 		    {
304 		      syntax_error (filename, lnum, _("missing key sequence"));
305 		      error = 1;
306 		    }
307 		}
308 	      else if (c == '\\')
309 		seqstate = slosh;
310 	      else if (c == '^')
311 		seqstate = control;
312 	      else
313 		To_seq (c);
314 	      break;
315 
316 	    case slosh:
317 	      switch (c)
318 		{
319 		case '0': case '1': case '2': case '3':
320 		case '4': case '5': case '6': case '7':
321 		  seqstate = octal;
322 		  oval = c - '0';
323 		  ocnt = 1;
324 		  break;
325 		case 'b':
326 		  To_seq ('\b');
327 		  seqstate = normal;
328 		  break;
329 		case 'e':
330 		  To_seq ('\033');
331 		  seqstate = normal;
332 		  break;
333 		case 'n':
334 		  To_seq ('\n');
335 		  seqstate = normal;
336 		  break;
337 		case 'r':
338 		  To_seq ('\r');
339 		  seqstate = normal;
340 		  break;
341 		case 't':
342 		  To_seq ('\t');
343 		  seqstate = normal;
344 		  break;
345 		case 'm':
346 		  meta = 1;
347 		  seqstate = normal;
348 		  break;
349 		case 'k':
350 		  seqstate = special_key;
351 		  break;
352 		default:
353 		  /* Backslash followed by any other char
354 		     just means that char.  */
355 		  To_seq (c);
356 		  seqstate = normal;
357 		  break;
358 		}
359 	      break;
360 
361 	    case octal:
362 	      switch (c)
363 		{
364 		case '0': case '1': case '2': case '3':
365 		case '4': case '5': case '6': case '7':
366 		  if (++ocnt <= 3)
367 		    oval = oval * 8 + c - '0';
368 		  if (ocnt == 3)
369 		    seqstate = normal;
370 		  break;
371 		default:
372 		  ocnt = 4;
373 		  seqstate = normal;
374 		  rescan = 1;
375 		  break;
376 		}
377 	      if (seqstate != octal)
378 		{
379 		  if (oval)
380 		    To_seq (oval);
381 		  else
382 		    {
383 		      syntax_error (filename, lnum,
384 				    _("NUL character (\\000) not permitted"));
385 		      error = 1;
386 		    }
387 		}
388 	      break;
389 
390 	    case special_key:
391 	      switch (c)
392 		{
393 		case 'u': To_seq (KEY_UP_ARROW); break;
394 		case 'd': To_seq (KEY_DOWN_ARROW); break;
395 		case 'r': To_seq (KEY_RIGHT_ARROW); break;
396 		case 'l': To_seq (KEY_LEFT_ARROW); break;
397 		case 'U': To_seq (KEY_PAGE_UP); break;
398 		case 'D': To_seq (KEY_PAGE_DOWN); break;
399 		case 'h': To_seq (KEY_HOME); break;
400 		case 'e': To_seq (KEY_END); break;
401 		case 'x': To_seq (KEY_DELETE); break;
402 		default:  To_seq (c); rescan = 1; break;
403 		}
404 	      seqstate = normal;
405 	      break;
406 
407 	    case control:
408 	      if (CONTROL (c))
409 		To_seq (CONTROL (c));
410 	      else
411 		{
412 		  syntax_error (filename, lnum,
413 				_("NUL character (^%c) not permitted"), c);
414 		  error = 1;
415 		}
416 	      seqstate = normal;
417 	      break;
418 	    }
419 	  break;
420 
421 	case got_keyseq:
422 	  if (isspace (c) && c != '\n')
423 	    break;
424 	  state = get_action;
425 	  alen = 0;
426 	  /* fall through */
427 	case get_action:
428 	  if (c == '\n' || isspace (c))
429 	    {
430 	      int a;
431 
432 	      state = got_action;
433 	      rescan = 1;
434 	      if (alen == 0)
435 		{
436 		  syntax_error (filename, lnum, _("missing action name"));
437 		  error = 1;
438 		}
439 	      else
440 		{
441                   int keymap_bind_keyseq (Keymap, int *, KEYMAP_ENTRY *);
442 
443 		  act[alen] = '\0';
444 		  a = lookup_action (act);
445                   if (a == A_info_menu_digit)
446 		    {
447                       /* Only allow "1 menu-digit".  (This is useful if
448                          this default binding is disabled with "#stop".)
449                          E.g. do not allow "b menu-digit".  */
450                       if (seq[0] != '1' || seq[1] != '\0'
451                           || section != info)
452                         {
453                           syntax_error (filename, lnum,
454                                  _("cannot bind key sequence to menu-digit"));
455                         }
456                       else
457                         {
458                           /* Bind each key from '1' to '9' to 'menu-digit'. */
459                           KEYMAP_ENTRY ke;
460                           int i;
461 
462                           ke.type = ISFUNC;
463                           ke.value.function = &function_doc_array[a];
464 
465                           for (i = '1'; i <= '9'; i++)
466                             {
467                               seq[0] = i;
468                               keymap_bind_keyseq (info_keymap, seq, &ke);
469                             }
470                         }
471 		    }
472 		  else if (a == -1)
473 		    {
474                       /* Print an error message, but keep going (don't set
475                          error = 1) for compatibility with infokey files aimed
476                          at future versions which may have different
477                          actions. */
478 		      syntax_error (filename, lnum, _("unknown action `%s'"),
479 				    act);
480 		    }
481                   else
482 		    {
483                       KEYMAP_ENTRY ke;
484                       static InfoCommand invalid_function = { 0 };
485 
486                       ke.type = ISFUNC;
487                       ke.value.function = a != A_INVALID
488                                             ? &function_doc_array[a]
489                                             : &invalid_function;
490                       To_seq (0);
491 
492                       if (section == info)
493                         keymap_bind_keyseq (info_keymap, seq, &ke);
494                       else /* section == ea */
495                         keymap_bind_keyseq (echo_area_keymap, seq, &ke);
496 		    }
497 		}
498 	    }
499 	  else if (alen < sizeof act - 1)
500 	    act[alen++] = c;
501 	  else
502 	    {
503 	      syntax_error (filename, lnum, _("action name too long"));
504 	      error = 1;
505 	    }
506 	  break;
507 
508 	case got_action:
509 	  if (c == '#')
510 	    state = in_trailing_comment;
511 	  else if (c == '\n')
512 	    state = start_of_line;
513 	  else if (!isspace (c))
514 	    {
515 	      syntax_error (filename, lnum,
516 			    _("extra characters following action `%s'"),
517 			    act);
518 	      error = 1;
519 	    }
520 	  break;
521 
522 	case get_varname:
523 	  if (c == '=')
524 	    {
525 	      if (varlen == 0)
526 		{
527 		  syntax_error (filename, lnum, _("missing variable name"));
528 		  error = 1;
529 		}
530 	      state = get_value;
531 	      vallen = 0;
532 	    }
533 	  else if (c == '\n' || isspace (c))
534 	    {
535 	      syntax_error (filename, lnum,
536 			    _("missing `=' immediately after variable name"));
537 	      error = 1;
538 	    }
539 	  else if (varlen < sizeof varn - 1)
540 	    varn[varlen++] = c;
541 	  else
542 	    {
543 	      syntax_error (filename, lnum, _("variable name too long"));
544 	      error = 1;
545 	    }
546 	  break;
547 
548 	case get_value:
549 	  if (c == '\n')
550 	    {
551               VARIABLE_ALIST *v;
552 
553               state = start_of_line;
554               varn[varlen] = '\0';
555               val[vallen] = '\0';
556               v = variable_by_name (varn);
557               if (!v)
558                 info_error (_("%s: no such variable"), varn);
559               else if (!set_variable_to_value (v, val, SET_IN_CONFIG_FILE))
560                 info_error (_("value %s is not valid for variable %s"),
561                               val, varn);
562 	    }
563 	  else if (vallen < sizeof val - 1)
564 	    val[vallen++] = c;
565 	  else
566 	    {
567 	      syntax_error (filename, lnum, _("value too long"));
568 	      error = 1;
569 	    }
570 	  break;
571 
572         case get_equals:
573         case got_equals:
574         case got_varname:
575           break;
576 	}
577     }
578 
579 #undef To_seq
580 
581   return !error;
582 }
583 
584 /* Return the numeric code of an Info command given its name.  If not found,
585    return -1.  This uses the auto-generated array in doc.c. */
586 static int
lookup_action(const char * name)587 lookup_action (const char *name)
588 {
589   int i;
590 
591   if (!strcmp (name, "invalid"))
592     return A_INVALID;
593   for (i = 0; function_doc_array[i].func_name; i++)
594     if (!strcmp (function_doc_array[i].func_name, name))
595       return i;
596   return -1;
597 }
598 
599 
600 
601 /* Error handling. */
602 
603 /* Give the user a generic error message in the form
604 	progname: message
605  */
606 static void
syntax_error(const char * filename,unsigned int linenum,const char * fmt,...)607 syntax_error (const char *filename,
608 	      unsigned int linenum, const char *fmt, ...)
609 {
610   va_list ap;
611 
612   fprintf (stderr, "%s: ", program_name);
613   fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
614   va_start(ap, fmt);
615   vfprintf (stderr, fmt, ap);
616   va_end(ap);
617   fprintf (stderr, "\n");
618 }
619 
620 
621