1 /***************************************************************************
2 * Pinfo is a ncurses based lynx style info documentation browser
3 *
4 * Copyright (C) 1999 Przemek Borys <pborys@dione.ids.pl>
5 * Copyright (C) 2005 Bas Zoetekouw <bas@debian.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 ***************************************************************************/
21
22 #include "common_includes.h"
23
24 #include <regex.h>
25 #include <ctype.h>
26 #include <sys/select.h>
27 #include <sys/wait.h>
28
29 #ifdef USE_WCHAR
30 #include <wchar.h>
31 #endif
32
33 char *safe_user = "nobody";
34 char *safe_group = "nogroup";
35
36 #ifndef HAVE_DECL_CURS_SET
37 void
curs_set(int a)38 curs_set(int a)
39 {
40 }
41 #endif
42
43 #ifdef ___DONT_USE_REGEXP_SEARCH___
44 char *pinfo_re_pattern = 0;
45 #else
46 int pinfo_re_offset = -1;
47 #endif
48
49 #ifdef HAS_READLINE
50 #include <readline/readline.h>
51 #include <readline/history.h>
52 /* HAS_READLINE */
53 #endif
54
55
56 /*
57 * the bellow define enables malloc/realloc/free logging to stderr.
58 * They start to log their argument values.
59 *
60 * #define ___DEBUG___
61 *
62 */
63
64 #ifdef ___DEBUG___
65 unsigned long malloc_addr[1000];
66 unsigned long msizes[1000];
67 long addrescount = 0;
68 /* ___DEBUG___ */
69 #endif
70
71
72 int curses_open = 0;
73
74 int shell_cursor = 1;
75
76 void
xfree(void * ptr)77 xfree(void *ptr)
78 {
79 #ifdef ___DEBUG___
80 int i, j;
81 int flag = 0;
82 unsigned long msize = 0;
83 for (i = 0; i < addrescount; i++)
84 msize += msizes[i];
85 fprintf(stderr, "Size: %lu, count: %ld, freeing %lu\n", msize, addrescount,(unsigned long) ptr);
86 for (i = 0; i < addrescount; i++)
87 if (malloc_addr[i] ==(unsigned long) ptr)
88 {
89 flag = 1;
90 for (j = i + 1; j < addrescount; j++)
91 {
92 malloc_addr[j - 1] = malloc_addr[j];
93 msizes[j - 1] = msizes[j];
94 }
95 addrescount--;
96 break;
97 }
98 if (flag == 0)
99 {
100 fprintf(stderr, "ERROR!!!\n");
101 getchar();
102 }
103 /* ___DEBUG___ */
104 #endif
105 free(ptr);
106 }
107
108 /* TODO: get rid of this xmalloc nonsense */
109 void *
xmalloc(size_t size)110 xmalloc(size_t size)
111 {
112 register void *value = malloc(size);
113 #ifdef ___DEBUG___
114 unsigned long msize = 0;
115 int i;
116 /* ___DEBUG___ */
117 #endif
118 if (value == 0)
119 {
120 closeprogram();
121 printf(_("Virtual memory exhausted\n"));
122 exit(1);
123 }
124 #ifdef ___DEBUG___
125 for (i = 0; i < addrescount; i++)
126 msize += msizes[i];
127 fprintf(stderr, "Size %lu, count: %ld, allocated %lu\n", msize, addrescount,(unsigned long) value);
128 malloc_addr[addrescount] =(unsigned long) value;
129 msizes[addrescount] = size;
130 if (addrescount < 1000)
131 addrescount++;
132 else
133 {
134 fprintf(stderr, "trace buffer exhausted\n");
135 }
136 /* ___DEBUG___ */
137 #endif
138 memset(value, 0, size);
139 return value;
140 }
141
142 void *
xrealloc(void * ptr,size_t size)143 xrealloc(void *ptr, size_t size)
144 {
145 #ifdef ___DEBUG___
146 int i, j, flag = 0;
147 register void *value;
148 unsigned long msize = 0;
149 for (i = 0; i < addrescount; i++)
150 msize += msizes[i];
151 fprintf(stderr, "Size: %lu, count: %ld, reallocating %lu to ", msize, addrescount,(unsigned long) ptr);
152 for (i = 0; i < addrescount; i++)
153 if (malloc_addr[i] ==(unsigned long) ptr)
154 {
155 flag = 1;
156 for (j = i + 1; j < addrescount; j++)
157 {
158 malloc_addr[j - 1] = malloc_addr[j];
159 msizes[j - 1] = msizes[j];
160 }
161 addrescount--;
162 break;
163 }
164 if (flag == 0)
165 {
166 fprintf(stderr, "ERROR!!!\n");
167 getchar();
168 }
169 value = realloc(ptr, size);
170 #else
171 register void *value = realloc(ptr, size + 1024);
172 /* ___DEBUG___ */
173 #endif
174 if (value == 0)
175 {
176 closeprogram();
177 printf(_("Virtual memory exhausted\n"));
178 exit(1);
179 }
180 #ifdef ___DEBUG___
181 fprintf(stderr, "%lu, with size %lu\n",(unsigned long) value,(unsigned long) size);
182 malloc_addr[addrescount] =(unsigned long) value;
183 msizes[addrescount] = size;
184 if (addrescount < 1000)
185 addrescount++;
186 else
187 {
188 fprintf(stderr, "trace buffer exhausted\n");
189 }
190 /* ___DEBUG___ */
191 #endif
192 return value;
193 }
194
195 int
system_check(const char * command)196 system_check(const char *command)
197 {
198 if (command==NULL)
199 {
200 return -1;
201 }
202 int result = system(command);
203 if (WIFEXITED(result))
204 {
205 return WEXITSTATUS(result);
206 }
207 return -1;
208 }
209
210 void
xsystem(const char * command)211 xsystem(const char *command)
212 {
213 int result = system_check(command);
214 if (result!=0)
215 {
216 printf(_("Failed to execute command '%s': %i"), command, result);
217 exit(2);
218 }
219 }
220
221 void
initlocale()222 initlocale()
223 {
224 #ifdef ___DEBUG___
225 int i;
226 for (i = 0; i < 1000; i++)
227 malloc_addr[i] = 0;
228 /* ___DEBUG___ */
229 #endif
230 setlocale(LC_ALL, "");
231 bindtextdomain(PACKAGE, LOCALEDIR);
232 textdomain(PACKAGE);
233 }
234
235 void
mymvhline(int y,int x,char ch,int len)236 mymvhline(int y, int x, char ch, int len)
237 {
238 int i;
239 for (i = 0; i < len; i++)
240 mvaddch(y, x + i, ch);
241 }
242
243 void
checkfilename(char * filename)244 checkfilename(char *filename)
245 {
246 if ((strchr(filename, '<')) ||
247 (strchr(filename, '>')) ||
248 (strchr(filename, '|')) ||
249 (strchr(filename, '(')) ||
250 (strchr(filename, ')')) ||
251 (strchr(filename, '!')) ||
252 (strchr(filename, '`')) ||
253 (strchr(filename, '&')) ||
254 (strchr(filename, ';')))
255 {
256 printf(_("Illegal characters in filename!\n*** %s\n"), filename);
257 exit(1);
258 }
259 }
260
261 #ifdef HAS_READLINE
262 /* custom function that readline will use to display text */
263 void
my_rl_display()264 my_rl_display()
265 {
266 static size_t len = 0;
267
268 /* if the user's input has changed, clear the entire line to remove possible leftover completions */
269 size_t newlen = strlen(rl_line_buffer);
270 if (newlen!=len)
271 {
272 mymvhline(maxy - 1, 0, ' ', maxx);
273 len = newlen;
274 }
275
276 /* go to the bottom line, print the prompt and buffer */
277 attrset(bottomline);
278 move(maxy-1,0);
279
280 printw("%s%s", rl_prompt, rl_line_buffer);
281 refresh();
282 }
283
284 void
my_rl_completion_display(char ** matches,int num_matches,int UNUSED (max_length))285 my_rl_completion_display(char **matches, int num_matches, int UNUSED(max_length))
286 {
287 if (num_matches<1)
288 {
289 return;
290 }
291 /* redraw entire prompt line, appended with possible matches */
292 move(maxy-1,0);
293 printw("%s", rl_prompt);
294 /* note: first entry is the entered text, matches start at index 1 */
295 printw("%s ", matches[0]);
296 for (int i=1; i < num_matches+1; i++)
297 {
298 printw("%s ", matches[i]);
299 }
300 /* and return prompt to correct position */
301 move(maxy-1, strlen(rl_prompt) + strlen(matches[0]) );
302
303 refresh();
304 }
305
306
307 /* note: if set, last string MUST be set to NULL */
308 static const char * const *completion_values = NULL;
309
310 /* readline completion functions, see https://thoughtbot.com/blog/tab-completion-in-gnu-readline */
311
312 /* this function is called for each attempted match */
313 char *
getstring_completion_generator(const char * text,int state)314 getstring_completion_generator(const char *text, int state)
315 {
316 static int list_index, len;
317 const char *name;
318
319 if (!state) {
320 list_index = 0;
321 len = strlen(text);
322 }
323
324 while ((name = completion_values[list_index++]) && name!=NULL) {
325 if (strncmp(name, text, len) == 0) {
326 return strdup(name);
327 }
328 }
329
330 return NULL;
331 }
332
333 /* this function is called when readline attempts completions. Return a matching function or NULL for no matching */
334 char **
getstring_completion(const char * text,int UNUSED (start),int UNUSED (end))335 getstring_completion(const char *text, int UNUSED(start), int UNUSED(end))
336 {
337 /* do not fall back to default filename completion */
338 rl_attempted_completion_over = 1;
339 rl_completion_append_character = '\0';
340
341 if (completion_values==NULL)
342 {
343 return NULL;
344 }
345 return rl_completion_matches(text, getstring_completion_generator);
346 }
347
348 #endif
349
350 const
completions_from_tag_table(TagTable * table,size_t num)351 char ** completions_from_tag_table(TagTable * table, size_t num)
352 {
353 /* allocate an extra entry at the end, which is set to NULL to terminate the table */
354 const char ** completions = calloc(num+1, sizeof(*completions));
355 for (size_t i=0, j=0; i<num; i++)
356 {
357 if (isalnum(table[i].nodename[0]))
358 {
359 completions[j++] = table[i].nodename;
360 }
361 }
362 return completions;
363 }
364
365
366 char *
getstring(char * prompt)367 getstring(char *prompt)
368 {
369 return getstring_with_completion(prompt, NULL);
370 }
371
372 char *
getstring_with_completion(char * prompt,const char * const * completions)373 getstring_with_completion(char *prompt, const char * const * completions)
374 {
375 char *buf;
376
377 #ifdef HAS_READLINE
378 completion_values = completions;
379 rl_attempted_completion_function = getstring_completion;
380 rl_completion_display_matches_hook = my_rl_completion_display;
381
382 curs_set(1);
383 mymvhline(maxy - 1, 0, ' ', maxx);
384 move(maxy - 1, 0);
385 refresh();
386
387 rl_readline_name = PACKAGE;
388
389 /* set display function for readline to my_rl_display and call readline */
390 rl_redisplay_function = my_rl_display;
391 buf = readline(prompt);
392 if (buf && *buf)
393 add_history(buf);
394
395 curs_set(0);
396
397 #else
398
399 move(maxy - 1, 0);
400 buf = readlinewrapper(prompt);
401
402 #endif
403
404 return buf;
405 }
406
407 void
init_curses()408 init_curses()
409 {
410 FILE *f = fopen("/dev/tty", "r+");
411 SCREEN *screen = newterm(NULL, f, f);
412 set_term(screen);
413 noecho();
414 cbreak();
415 keypad(stdscr, TRUE);
416 /* meta(stdscr, TRUE); */
417 initcolors();
418 shell_cursor = curs_set(0);
419 #ifdef CURSES_MOUSE
420 if (grab_mouse)
421 {
422 mousemask(BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED, NULL);
423 }
424 #endif
425 curses_open = 1;
426 }
427
428
429 void
closeprogram()430 closeprogram()
431 {
432 if (curses_open)
433 myendwin();
434 if (ClearScreenAtExit)
435 xsystem("clear");
436 else
437 printf("\n");
438 if (tmpfilename1)
439 {
440 unlink(tmpfilename1);
441 xfree(tmpfilename1);
442 }
443 if (tmpfilename2)
444 {
445 unlink(tmpfilename2);
446 xfree(tmpfilename2);
447 }
448 }
449
450 int
gettagtablepos_search_internal(char * node,int left,int right)451 gettagtablepos_search_internal(char *node, int left, int right)
452 {
453 /* left+(right-left)/2 */
454 int thispos = left +((right - left) >> 1);
455 int compare_result = compare_tag_table_string(tag_table[thispos].nodename, node);
456 if (compare_result == 0)
457 return thispos;
458 else
459 {
460 if (left == right)
461 return -1;
462 if (compare_result > 0)
463 {
464 if (thispos > left)
465 return gettagtablepos_search_internal(node, left, thispos - 1);
466 else
467 return -1;
468 }
469 else if (compare_result < 0)
470 {
471 if (thispos < right)
472 return gettagtablepos_search_internal(node, thispos + 1, right);
473 else
474 return -1;
475 }
476 }
477 return -1;
478 }
479
480 int
gettagtablepos(char * node)481 gettagtablepos(char *node)
482 {
483 /* strip spaces from the beginning */
484 while (1)
485 {
486 if ((*node != ' ') &&(*node != '\t'))
487 break;
488 node++;
489 }
490 return gettagtablepos_search_internal(node, 1, TagTableEntries);
491 }
492
493 int
pinfo_getch()494 pinfo_getch()
495 {
496 int key = getch();
497 /* following key will be alt's value */
498 if (key == META_KEY)
499 {
500 key = getch();
501 key |= 0x200;
502 }
503 return key;
504 }
505
506 void
waitforgetch()507 waitforgetch()
508 {
509 int ret;
510
511 fd_set rdfs;
512 FD_ZERO(&rdfs);
513 FD_SET(0, &rdfs);
514
515 /* we might get interrupted by e.g. SIGTSTP/SIGCONT */
516 do ret = select(1, &rdfs, NULL, NULL, NULL);
517 while (ret == -1 && errno == EINTR);
518 }
519
520 /* returns 0 on success, 1 on error */
521 int
pinfo_re_comp(char * name)522 pinfo_re_comp(char *name)
523 {
524 #ifdef ___DONT_USE_REGEXP_SEARCH___
525 if (pinfo_re_pattern)
526 {
527 free(pinfo_re_pattern);
528 pinfo_re_pattern = 0;
529 }
530 pinfo_re_pattern = strdup(name);
531 return 0;
532 #else
533 /* first see if we can compile the regexp */
534 regex_t preg;
535 if (regcomp(&preg, name, REG_ICASE) != 0)
536 {
537 /* compilation failed, so return */
538 return -1;
539 }
540
541 /* compilation succeeded */
542 /* first make some space in h_regexp[] to store the compiled regexp */
543 if (pinfo_re_offset == -1)
544 {
545 pinfo_re_offset = h_regexp_num;
546 if (!h_regexp_num)
547 h_regexp = malloc(sizeof(regex_t));
548 else
549 h_regexp = realloc(h_regexp, sizeof(regex_t) *(h_regexp_num + 1));
550 }
551 else
552 {
553 regfree(&h_regexp[pinfo_re_offset]);
554 }
555
556 /* then copy the compiled expression into the newly allocated space */
557 memcpy(&h_regexp[pinfo_re_offset], &preg, sizeof(preg));
558
559 /* and finally return 0 for success */
560 return 0;
561 #endif
562 }
563
564 int
pinfo_re_exec(char * name)565 pinfo_re_exec(char *name)
566 {
567 #ifdef ___DONT_USE_REGEXP_SEARCH___
568 char *found;
569 if (pinfo_re_pattern)
570 {
571 found = strstr(name, pinfo_re_pattern);
572 if (found != NULL)
573 return 1;
574 else
575 return 0;
576 }
577 #else
578 regmatch_t pmatch[1];
579 return !regexec(&h_regexp[pinfo_re_offset], name, 1, pmatch, 0);
580 #endif
581 }
582
583 int
yesno(char * prompt,int def)584 yesno(char *prompt, int def)
585 {
586 char *yes = _("yes");
587 char *no = _("no");
588 int key;
589
590 attrset(bottomline);
591 mymvhline(maxy - 1, 0, ' ', maxx);
592 move(maxy - 1, 0);
593 /* if default answer is yes */
594 if (def)
595 printw("%s([%c]/%c)", prompt, *yes, *no);
596 else
597 printw("%s([%c]/%c)", prompt, *no, *yes);
598 nodelay(stdscr, FALSE);
599 while (1)
600 {
601 key = getch();
602 if (key == ERR)
603 return -1;
604 if (is_enter_key(key))
605 break;
606 else
607 {
608 if (tolower(key) == tolower(*yes))
609 {
610 def = 1;
611 break;
612 }
613 else
614 {
615 if (tolower(key) == tolower(*no))
616 {
617 def = 0;
618 break;
619 }
620 else
621 beep();
622 }
623 }
624 }
625
626 nodelay(stdscr, TRUE);
627 if (def)
628 addstr(yes);
629 else
630 addstr(no);
631 attrset(normal);
632 return def;
633 }
634
635 void
myclrtoeol()636 myclrtoeol()
637 {
638 unsigned x, y;
639 getyx(stdscr, y, x);
640 for (unsigned i = x; i < maxx; i++)
641 mvaddch(y, i, ' ');
642 }
643
644 void
copy_stripped_from_regexp(char * src,char * dest)645 copy_stripped_from_regexp(char *src, char *dest)
646 {
647 char *forbidden = "*.\\()[]\n";
648 while (strchr(forbidden, *src) == NULL)
649 {
650 if (*src == 0)
651 break;
652 *dest = *src;
653 src++;
654 dest++;
655 }
656 *dest = 0;
657 }
658
659 void
myendwin()660 myendwin()
661 {
662 curs_set(shell_cursor);
663 endwin();
664 }
665
666 void
handlewinch()667 handlewinch()
668 {
669 myendwin();
670 init_curses();
671 doupdate();
672 getmaxyx(stdscr, maxy, maxx);
673 ungetch(keys.refresh_1);
674 }
675
676 /*
677 * this functions checks whether the node header node_header
678 * corresponds to node node_name
679 *
680 * e.g. the header is something like:
681 * File: bash.info, Node: Introduction, Next: Defs, Prev: Top, Up: Top
682 * and we check here if the Node: entry in this header is equal to node_name
683 *
684 * returns 0 if node_header does not belong to a node with name node_name
685 * returns -1 if no checking was done
686 * returns 1 if check turned out ok
687 */
688 int
check_node_name(const char * const node_name,const char * const node_header)689 check_node_name( const char * const node_name, const char * const node_header)
690 {
691 size_t header_len;
692 char *header, *str_start, *c;
693 int res;
694
695 /* if either one of node_name or node_header is NULL or a zero
696 * sized string, we have nothing to check, so return success */
697 if ( (node_name==NULL) || (node_header==NULL)
698 || (strlen(node_name)==0) || (strlen(node_header)==0) )
699 {
700 return 1;
701 }
702
703 header_len = strlen(node_header);
704
705 /* copy node_header to a local string which can be mutilated */
706 /* don't use strdup here, as xmalloc handles all errors */
707 header = xmalloc( header_len + 1 );
708 strcpy(header, node_header);
709
710 /* search for "Node: foobar," in node_header */
711 str_start = strstr(header, "Node: ");
712 if (str_start==NULL) /* no match */
713 {
714 return 0;
715 }
716 /* advance str_start to the start of the node name */
717 str_start += strlen("Node: ");
718 /* and search for the next comma, tab, or newline */
719 c = str_start;
720 while ( (*c!=',') && (*c!='\t') && (*c!='\n') && (*c!='\0') ) c++;
721 *c = '\0';
722
723 /* so, now str_start point to a \0-terminated string containing the
724 * node name from the header.
725 * Let's compare it with the node_name we're looking for */
726 res = strcmp(str_start, node_name);
727
728 /* we're done, so free alloc'ed vars */
729 xfree(header);
730
731 /* check result of strcmp() and return */
732 if ( res==0 )
733 {
734 /* match found */
735 return 1;
736 }
737 else
738 {
739 /* no match */
740 return 0;
741 }
742 }
743
744
745 /*
746 * The wcswidth function returns the number of columns needed to represent
747 * the wide-character string pointed to by s, but at most n wide charac‐
748 * ters. If a non-printable wide character occurs among these characters,
749 * -1 is returned.
750 */
751 #if defined(USE_WCHAR) && !defined(HAVE_WCSWIDTH)
752 int
wcswidth(const wchar_t * wstr,size_t max_len)753 wcswidth(const wchar_t *wstr, size_t max_len)
754 {
755 int width = 0;
756 size_t i;
757 size_t len = wcslen(wstr);
758
759 /* never count more than max_len chars */
760 if (len>max_len) len=max_len;
761
762 for (i=0; i<len; i++)
763 {
764 if (!iswprint(wstr[i])) return -1;
765 width += wcwidth(wstr[i]);
766 }
767
768 return width;
769 }
770 #endif /* USE_WCHAR && !HAVE_WCSWIDTH */
771
772
773 /* calculcate length of string, handling multibyte strings correctly
774 * returns value <= len
775 */
776 int
width_of_string(const char * const mbs,const int len)777 width_of_string( const char * const mbs, const int len)
778 {
779 int width;
780 char *str;
781 #ifdef USE_WCHAR
782 wchar_t *wstr;
783 #endif /* USE_WCHAR */
784
785 if (len<0) return -1;
786 if (len==0) return 0;
787
788 /* copy the string to a local buffer, because we only want to
789 * compare the first len bytes */
790 str = xmalloc(len+1);
791 memcpy(str, mbs, len);
792
793 #ifdef USE_WCHAR
794
795 /* allocate a widestring */
796 wstr = xmalloc( (len+1)*sizeof(wchar_t) );
797
798 mbstowcs(wstr, str, len);
799 width = wcswidth(wstr, len);
800
801 /* clean up */
802 xfree(wstr);
803
804 #else /* USE_WCHAR */
805
806 width = strlen(str);
807
808 #endif /* USE_WCHAR */
809
810 /* clean up */
811 xfree(str);
812
813 return width;
814 }
815
816 /*
817 * calculates the length of string between start and end, counting `\t' as
818 * filling up to 8 chars. (i.e. at line 22 tab will increment the counter by 2
819 * [8-(22-int(22/8)*8)] spaces)
820 */
821 int
calculate_len(char * start,char * end)822 calculate_len(char *start, char *end)
823 {
824 int len = 0;
825 char *c = start;
826 while (c < end)
827 {
828 if (*c == '\t')
829 {
830 /* now, first count everything leading up to this position */
831 len += width_of_string(start, c - start);
832 start = c+1;
833 /* then add the extra width of the tab */
834 len = ( len & ~0x07 ) + 0x08;
835 }
836 c++;
837 }
838 /* then count everything after the last tab */
839 len += width_of_string(start, c - start);
840
841 return len;
842 }
843
844 /*
845 * create a temporary file in a safe way, and return its name in a newly
846 * allocated string
847 */
848 char *
make_tempfile()849 make_tempfile()
850 {
851 char *filename;
852 size_t len;
853
854 /* TODO: fix hardcoded /tmp */
855 char tmpfile_template[] = "/tmp/pinfo.XXXXXX";
856
857 /* create a tmpfile */
858 int fd = mkstemp(tmpfile_template);
859 /* bug out if it failed */
860 if (fd == -1)
861 {
862 closeprogram();
863 printf(_("Couldn't open temporary file\n"));
864 exit(1);
865 }
866
867 /* allocate a new string and copy the filename there */
868 len = strlen(tmpfile_template)+1;
869 filename = xmalloc(len+1); /* guarenteerd to be set to \0's */
870 strncpy(filename, tmpfile_template, len);
871
872 /* close the file */
873 close(fd);
874
875 return filename;
876 }
877