1 /*
2    Input line filename/username/hostname/variable/command completion.
3    (Let mc type for you...)
4 
5    Copyright (C) 1995-2021
6    Free Software Foundation, Inc.
7 
8    Written by:
9    Jakub Jelinek, 1995
10    Slava Zanko <slavazanko@gmail.com>, 2013
11    Andrew Borodin <aborodin@vmail.ru>, 2013
12 
13    This file is part of the Midnight Commander.
14 
15    The Midnight Commander is free software: you can redistribute it
16    and/or modify it under the terms of the GNU General Public License as
17    published by the Free Software Foundation, either version 3 of the License,
18    or (at your option) any later version.
19 
20    The Midnight Commander is distributed in the hope that it will be useful,
21    but WITHOUT ANY WARRANTY; without even the implied warranty of
22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23    GNU General Public License for more details.
24 
25    You should have received a copy of the GNU General Public License
26    along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  */
28 
29 /** \file lib/widget/input_complete.c
30  *  \brief Source: Input line filename/username/hostname/variable/command completion
31  */
32 
33 #include <config.h>
34 
35 #include <ctype.h>
36 #include <limits.h>             /* MB_LEN_MAX */
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <dirent.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <pwd.h>
44 #include <unistd.h>
45 
46 #include "lib/global.h"
47 
48 #include "lib/tty/tty.h"
49 #include "lib/tty/key.h"        /* XCTRL and ALT macros */
50 #include "lib/vfs/vfs.h"
51 #include "lib/strescape.h"
52 #include "lib/strutil.h"
53 #include "lib/util.h"
54 #include "lib/widget.h"
55 
56 /*** global variables ****************************************************************************/
57 
58 /* Linux declares environ in <unistd.h>, so don't repeat it here. */
59 #if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
60 extern char **environ;
61 #endif
62 
63 /*** file scope macro definitions ****************************************************************/
64 
65 /* #define DO_COMPLETION_DEBUG */
66 #ifdef DO_COMPLETION_DEBUG
67 #define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
68 #else
69 #define SHOW_C_CTX(func)
70 #endif /* DO_CMPLETION_DEBUG */
71 
72 #define DO_INSERTION 1
73 #define DO_QUERY     2
74 
75 /*** file scope type declarations ****************************************************************/
76 
77 typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
78 
79 typedef struct
80 {
81     size_t in_command_position;
82     char *word;
83     char *p;
84     char *q;
85     char *r;
86     gboolean is_cd;
87     input_complete_t flags;
88 } try_complete_automation_state_t;
89 
90 /*** file scope variables ************************************************************************/
91 
92 static char **hosts = NULL;
93 static char **hosts_p = NULL;
94 static int hosts_alloclen = 0;
95 
96 static int complete_height, complete_width;
97 static WInput *input;
98 static int min_end;
99 static int start = 0;
100 static int end = 0;
101 
102 /*** file scope functions ************************************************************************/
103 /* --------------------------------------------------------------------------------------------- */
104 
105 char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
106 void complete_engine_fill_completions (WInput * in);
107 
108 #ifdef DO_COMPLETION_DEBUG
109 /**
110  * Useful to print/debug completion flags
111  */
112 static const char *
show_c_flags(input_complete_t flags)113 show_c_flags (input_complete_t flags)
114 {
115     static char s_cf[] = "FHCVUDS";
116 
117     s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
118     s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
119     s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
120     s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
121     s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
122     s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
123     s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
124 
125     return s_cf;
126 }
127 #endif /* DO_CMPLETION_DEBUG */
128 
129 /* --------------------------------------------------------------------------------------------- */
130 
131 static char *
filename_completion_function(const char * text,int state,input_complete_t flags)132 filename_completion_function (const char *text, int state, input_complete_t flags)
133 {
134     static DIR *directory = NULL;
135     static char *filename = NULL;
136     static char *dirname = NULL;
137     static char *users_dirname = NULL;
138     static size_t filename_len = 0;
139     static vfs_path_t *dirname_vpath = NULL;
140 
141     gboolean isdir = TRUE, isexec = FALSE;
142     struct vfs_dirent *entry = NULL;
143 
144     SHOW_C_CTX ("filename_completion_function");
145 
146     if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
147     {
148         char *u_text;
149         char *result;
150         char *e_result;
151 
152         u_text = strutils_shell_unescape (text);
153 
154         result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
155         g_free (u_text);
156 
157         e_result = strutils_shell_escape (result);
158         g_free (result);
159 
160         return e_result;
161     }
162 
163     /* If we're starting the match process, initialize us a bit. */
164     if (state == 0)
165     {
166         const char *temp;
167 
168         g_free (dirname);
169         g_free (filename);
170         g_free (users_dirname);
171         vfs_path_free (dirname_vpath, TRUE);
172 
173         if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
174         {
175             filename = g_strdup (++temp);
176             dirname = g_strndup (text, temp - text);
177         }
178         else
179         {
180             dirname = g_strdup (".");
181             filename = g_strdup (text);
182         }
183 
184         /* We aren't done yet.  We also support the "~user" syntax. */
185 
186         /* Save the version of the directory that the user typed. */
187         users_dirname = dirname;
188         dirname = tilde_expand (dirname);
189         canonicalize_pathname (dirname);
190         dirname_vpath = vfs_path_from_str (dirname);
191 
192         /* Here we should do something with variable expansion
193            and `command`.
194            Maybe a dream - UNIMPLEMENTED yet. */
195 
196         directory = mc_opendir (dirname_vpath);
197         filename_len = strlen (filename);
198     }
199 
200     /* Now that we have some state, we can read the directory. */
201 
202     while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
203     {
204         if (!str_is_valid_string (entry->d_name))
205             continue;
206 
207         /* Special case for no filename.
208            All entries except "." and ".." match. */
209         if (filename_len == 0)
210         {
211             if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
212                 continue;
213         }
214         else
215         {
216             /* Otherwise, if these match up to the length of filename, then
217                it may be a match. */
218             if ((entry->d_name[0] != filename[0]) ||
219                 ((NLENGTH (entry)) < filename_len) ||
220                 strncmp (filename, entry->d_name, filename_len) != 0)
221                 continue;
222         }
223 
224         isdir = TRUE;
225         isexec = FALSE;
226 
227         {
228             struct stat tempstat;
229             vfs_path_t *tmp_vpath;
230 
231             tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
232 
233             /* Unix version */
234             if (mc_stat (tmp_vpath, &tempstat) == 0)
235             {
236                 uid_t my_uid;
237                 gid_t my_gid;
238 
239                 my_uid = getuid ();
240                 my_gid = getgid ();
241 
242                 if (!S_ISDIR (tempstat.st_mode))
243                 {
244                     isdir = FALSE;
245 
246                     if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
247                         (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
248                         (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
249                         (tempstat.st_mode & 0001) != 0)
250                         isexec = TRUE;
251                 }
252             }
253             else
254             {
255                 /* stat failed, strange. not a dir in any case */
256                 isdir = FALSE;
257             }
258             vfs_path_free (tmp_vpath, TRUE);
259         }
260 
261         if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
262             break;
263         if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
264             break;
265         if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
266             break;
267     }
268 
269     if (entry == NULL)
270     {
271         if (directory != NULL)
272         {
273             mc_closedir (directory);
274             directory = NULL;
275         }
276         MC_PTR_FREE (dirname);
277         vfs_path_free (dirname_vpath, TRUE);
278         dirname_vpath = NULL;
279         MC_PTR_FREE (filename);
280         MC_PTR_FREE (users_dirname);
281         return NULL;
282     }
283 
284     {
285         GString *temp;
286 
287         temp = g_string_sized_new (16);
288 
289         if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
290         {
291             g_string_append (temp, users_dirname);
292 
293             /* We need a '/' at the end. */
294             if (!IS_PATH_SEP (temp->str[temp->len - 1]))
295                 g_string_append_c (temp, PATH_SEP);
296         }
297         g_string_append (temp, entry->d_name);
298         if (isdir)
299             g_string_append_c (temp, PATH_SEP);
300 
301         return g_string_free (temp, FALSE);
302     }
303 }
304 
305 /* --------------------------------------------------------------------------------------------- */
306 /** We assume here that text[0] == '~' , if you want to call it in another way,
307    you have to change the code */
308 
309 static char *
username_completion_function(const char * text,int state,input_complete_t flags)310 username_completion_function (const char *text, int state, input_complete_t flags)
311 {
312     static struct passwd *entry = NULL;
313     static size_t userlen = 0;
314 
315     (void) flags;
316     SHOW_C_CTX ("username_completion_function");
317 
318     if (text[0] == '\\' && text[1] == '~')
319         text++;
320     if (state == 0)
321     {                           /* Initialization stuff */
322         setpwent ();
323         userlen = strlen (text + 1);
324     }
325 
326     while ((entry = getpwent ()) != NULL)
327     {
328         /* Null usernames should result in all users as possible completions. */
329         if (userlen == 0)
330             break;
331         if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
332             break;
333     }
334 
335     if (entry != NULL)
336         return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
337 
338     endpwent ();
339     return NULL;
340 }
341 
342 /* --------------------------------------------------------------------------------------------- */
343 /** We assume text [0] == '$' and want to have a look at text [1], if it is
344    equal to '{', so that we should append '}' at the end */
345 
346 static char *
variable_completion_function(const char * text,int state,input_complete_t flags)347 variable_completion_function (const char *text, int state, input_complete_t flags)
348 {
349     static char **env_p = NULL;
350     static gboolean isbrace = FALSE;
351     static size_t varlen = 0;
352     const char *p = NULL;
353 
354     (void) flags;
355     SHOW_C_CTX ("variable_completion_function");
356 
357     if (state == 0)
358     {                           /* Initialization stuff */
359         isbrace = (text[1] == '{');
360         varlen = strlen (text + 1 + isbrace);
361         env_p = environ;
362     }
363 
364     while (*env_p != NULL)
365     {
366         p = strchr (*env_p, '=');
367         if (p != NULL && ((size_t) (p - *env_p) >= varlen)
368             && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
369             break;
370         env_p++;
371     }
372 
373     if (*env_p == NULL)
374         return NULL;
375 
376     {
377         GString *temp;
378 
379         temp = g_string_new_len (*env_p, p - *env_p);
380 
381         if (isbrace)
382         {
383             g_string_prepend_c (temp, '{');
384             g_string_append_c (temp, '}');
385         }
386         g_string_prepend_c (temp, '$');
387 
388         env_p++;
389 
390         return g_string_free (temp, FALSE);
391     }
392 }
393 
394 /* --------------------------------------------------------------------------------------------- */
395 
396 static void
fetch_hosts(const char * filename)397 fetch_hosts (const char *filename)
398 {
399     FILE *file;
400     char buffer[256];
401     char *name;
402     char *lc_start;
403     char *bi;
404 
405     file = fopen (filename, "r");
406     if (file == NULL)
407         return;
408 
409     while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
410     {
411         /* Skip to first character. */
412         for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
413             ;
414 
415         /* Ignore comments... */
416         if (bi[0] == '#')
417             continue;
418 
419         /* Handle $include. */
420         if (strncmp (bi, "$include ", 9) == 0)
421         {
422             char *includefile, *t;
423 
424             /* Find start of filename. */
425             includefile = bi + 9;
426             while (*includefile != '\0' && whitespace (*includefile))
427                 includefile++;
428             t = includefile;
429 
430             /* Find end of filename. */
431             while (t[0] != '\0' && !str_isspace (t))
432                 str_next_char (&t);
433             *t = '\0';
434 
435             fetch_hosts (includefile);
436             continue;
437         }
438 
439         /* Skip IP #s. */
440         while (bi[0] != '\0' && !str_isspace (bi))
441             str_next_char (&bi);
442 
443         /* Get the host names separated by white space. */
444         while (bi[0] != '\0' && bi[0] != '#')
445         {
446             while (bi[0] != '\0' && str_isspace (bi))
447                 str_next_char (&bi);
448             if (bi[0] == '#')
449                 continue;
450             for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
451                 ;
452 
453             if (bi == lc_start)
454                 continue;
455 
456             name = g_strndup (lc_start, bi - lc_start);
457 
458             {
459                 char **host_p;
460                 int j;
461 
462                 j = hosts_p - hosts;
463 
464                 if (j >= hosts_alloclen)
465                 {
466                     hosts_alloclen += 30;
467                     hosts = g_renew (char *, hosts, hosts_alloclen + 1);
468                     hosts_p = hosts + j;
469                 }
470 
471                 for (host_p = hosts; host_p < hosts_p; host_p++)
472                     if (strcmp (name, *host_p) == 0)
473                         break;  /* We do not want any duplicates */
474 
475                 if (host_p == hosts_p)
476                 {
477                     *(hosts_p++) = name;
478                     *hosts_p = NULL;
479                 }
480                 else
481                     g_free (name);
482             }
483         }
484     }
485 
486     fclose (file);
487 }
488 
489 /* --------------------------------------------------------------------------------------------- */
490 
491 static char *
hostname_completion_function(const char * text,int state,input_complete_t flags)492 hostname_completion_function (const char *text, int state, input_complete_t flags)
493 {
494     static char **host_p = NULL;
495     static size_t textstart = 0;
496     static size_t textlen = 0;
497 
498     (void) flags;
499     SHOW_C_CTX ("hostname_completion_function");
500 
501     if (state == 0)
502     {                           /* Initialization stuff */
503         const char *p;
504 
505         g_strfreev (hosts);
506         hosts_alloclen = 30;
507         hosts = g_new (char *, hosts_alloclen + 1);
508         *hosts = NULL;
509         hosts_p = hosts;
510         p = getenv ("HOSTFILE");
511         fetch_hosts (p != NULL ? p : "/etc/hosts");
512         host_p = hosts;
513         textstart = (*text == '@') ? 1 : 0;
514         textlen = strlen (text + textstart);
515     }
516 
517     for (; *host_p != NULL; host_p++)
518     {
519         if (textlen == 0)
520             break;              /* Match all of them */
521         if (strncmp (text + textstart, *host_p, textlen) == 0)
522             break;
523     }
524 
525     if (*host_p == NULL)
526     {
527         g_strfreev (hosts);
528         hosts = NULL;
529         return NULL;
530     }
531 
532     {
533         GString *temp;
534 
535         temp = g_string_sized_new (8);
536 
537         if (textstart != 0)
538             g_string_append_c (temp, '@');
539         g_string_append (temp, *host_p);
540         host_p++;
541 
542         return g_string_free (temp, FALSE);
543     }
544 }
545 
546 /* --------------------------------------------------------------------------------------------- */
547 /**
548  * This is the function to call when the word to complete is in a position
549  * where a command word can be found. It looks around $PATH, looking for
550  * commands that match. It also scans aliases, function names, and the
551  * table of shell built-ins.
552  */
553 
554 static char *
command_completion_function(const char * text,int state,input_complete_t flags)555 command_completion_function (const char *text, int state, input_complete_t flags)
556 {
557     static const char *path_end = NULL;
558     static gboolean isabsolute = FALSE;
559     static int phase = 0;
560     static size_t text_len = 0;
561     static const char *const *words = NULL;
562     static char *path = NULL;
563     static char *cur_path = NULL;
564     static char *cur_word = NULL;
565     static int init_state = 0;
566     static const char *const bash_reserved[] = {
567         "if", "then", "else", "elif", "fi", "case", "esac", "for",
568         "select", "while", "until", "do", "done", "in", "function", 0
569     };
570     static const char *const bash_builtins[] = {
571         "alias", "bg", "bind", "break", "builtin", "cd", "command",
572         "continue", "declare", "dirs", "echo", "enable", "eval",
573         "exec", "exit", "export", "fc", "fg", "getopts", "hash",
574         "help", "history", "jobs", "kill", "let", "local", "logout",
575         "popd", "pushd", "pwd", "read", "readonly", "return", "set",
576         "shift", "source", "suspend", "test", "times", "trap", "type",
577         "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
578     };
579 
580     char *u_text;
581     char *p, *found;
582 
583     SHOW_C_CTX ("command_completion_function");
584 
585     if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
586         return NULL;
587 
588     u_text = strutils_shell_unescape (text);
589     flags &= ~INPUT_COMPLETE_SHELL_ESC;
590 
591     if (state == 0)
592     {                           /* Initialize us a little bit */
593         isabsolute = strchr (u_text, PATH_SEP) != NULL;
594         if (!isabsolute)
595         {
596             words = bash_reserved;
597             phase = 0;
598             text_len = strlen (u_text);
599 
600             if (path == NULL)
601             {
602                 path = g_strdup (getenv ("PATH"));
603                 if (path != NULL)
604                 {
605                     p = path;
606                     path_end = strchr (p, '\0');
607                     while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
608                         *p++ = '\0';
609                 }
610             }
611         }
612     }
613 
614     if (isabsolute)
615     {
616         p = filename_completion_function (u_text, state, flags);
617 
618         if (p != NULL)
619         {
620             char *temp_p = p;
621 
622             p = strutils_shell_escape (p);
623             g_free (temp_p);
624         }
625 
626         g_free (u_text);
627         return p;
628     }
629 
630     found = NULL;
631     switch (phase)
632     {
633     case 0:                    /* Reserved words */
634         for (; *words != NULL; words++)
635             if (strncmp (*words, u_text, text_len) == 0)
636             {
637                 g_free (u_text);
638                 return g_strdup (*(words++));
639             }
640         phase++;
641         words = bash_builtins;
642         MC_FALLTHROUGH;
643     case 1:                    /* Builtin commands */
644         for (; *words != NULL; words++)
645             if (strncmp (*words, u_text, text_len) == 0)
646             {
647                 g_free (u_text);
648                 return g_strdup (*(words++));
649             }
650         phase++;
651         if (path == NULL)
652             break;
653         cur_path = path;
654         cur_word = NULL;
655         MC_FALLTHROUGH;
656     case 2:                    /* And looking through the $PATH */
657         while (found == NULL)
658         {
659             if (cur_word == NULL)
660             {
661                 char *expanded;
662 
663                 if (cur_path >= path_end)
664                     break;
665                 expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
666                 cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
667                 g_free (expanded);
668                 canonicalize_pathname (cur_word);
669                 cur_path = strchr (cur_path, '\0') + 1;
670                 init_state = state;
671             }
672             found = filename_completion_function (cur_word, state - init_state, flags);
673             if (found == NULL)
674                 MC_PTR_FREE (cur_word);
675         }
676         MC_FALLTHROUGH;
677     default:
678         break;
679     }
680 
681     if (found == NULL)
682         MC_PTR_FREE (path);
683     else
684     {
685         p = strrchr (found, PATH_SEP);
686         if (p != NULL)
687         {
688             char *tmp = found;
689 
690             found = strutils_shell_escape (p + 1);
691             g_free (tmp);
692         }
693     }
694 
695     g_free (u_text);
696     return found;
697 }
698 
699 /* --------------------------------------------------------------------------------------------- */
700 
701 static int
match_compare(const void * a,const void * b)702 match_compare (const void *a, const void *b)
703 {
704     return strcmp (*(char *const *) a, *(char *const *) b);
705 }
706 
707 /* --------------------------------------------------------------------------------------------- */
708 /** Returns an array of char * matches with the longest common denominator
709    in the 1st entry. Then a NULL terminated list of different possible
710    completions follows.
711    You have to supply your own CompletionFunction with the word you
712    want to complete as the first argument and an count of previous matches
713    as the second.
714    In case no matches were found we return NULL. */
715 
716 static char **
completion_matches(const char * text,CompletionFunction entry_function,input_complete_t flags)717 completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
718 {
719     /* Number of slots in match_list. */
720     size_t match_list_size = 30;
721     /* The list of matches. */
722     char **match_list;
723     /* Number of matches actually found. */
724     size_t matches = 0;
725 
726     /* Temporary string binder. */
727     char *string;
728 
729     match_list = g_new (char *, match_list_size + 1);
730     match_list[1] = NULL;
731 
732     while ((string = (*entry_function) (text, matches, flags)) != NULL)
733     {
734         if (matches + 1 == match_list_size)
735         {
736             match_list_size += 30;
737             match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
738         }
739         match_list[++matches] = string;
740         match_list[matches + 1] = NULL;
741     }
742 
743     /* If there were any matches, then look through them finding out the
744        lowest common denominator.  That then becomes match_list[0]. */
745     if (matches == 0)
746         MC_PTR_FREE (match_list);       /* There were no matches. */
747     else
748     {
749         /* If only one match, just use that. */
750         if (matches == 1)
751         {
752             match_list[0] = match_list[1];
753             match_list[1] = NULL;
754         }
755         else
756         {
757             size_t i = 1;
758             int low = 4096;     /* Count of max-matched characters. */
759             size_t j;
760 
761             qsort (match_list + 1, matches, sizeof (char *), match_compare);
762 
763             /* And compare each member of the list with
764                the next, finding out where they stop matching.
765                If we find two equal strings, we have to put one away... */
766 
767             j = i + 1;
768             while (j < matches + 1)
769             {
770                 char *si, *sj;
771                 char *ni, *nj;
772 
773                 for (si = match_list[i], sj = match_list[j]; si[0] != '\0' && sj[0] != '\0';)
774                 {
775 
776                     ni = str_get_next_char (si);
777                     nj = str_get_next_char (sj);
778 
779                     if (ni - si != nj - sj)
780                         break;
781                     if (strncmp (si, sj, ni - si) != 0)
782                         break;
783 
784                     si = ni;
785                     sj = nj;
786                 }
787 
788                 if (si[0] == '\0' && sj[0] == '\0')
789                 {               /* Two equal strings */
790                     g_free (match_list[j]);
791                     j++;
792                     if (j > matches)
793                         break;
794                     continue;   /* Look for a run of equal strings */
795                 }
796                 else if (low > si - match_list[i])
797                     low = si - match_list[i];
798                 if (i + 1 != j) /* So there's some gap */
799                     match_list[i + 1] = match_list[j];
800                 i++;
801                 j++;
802             }
803             matches = i;
804             match_list[matches + 1] = NULL;
805             match_list[0] = g_strndup (match_list[1], low);
806         }
807     }
808 
809     return match_list;
810 }
811 
812 /* --------------------------------------------------------------------------------------------- */
813 /** Check if directory completion is needed */
814 static gboolean
check_is_cd(const char * text,int lc_start,input_complete_t flags)815 check_is_cd (const char *text, int lc_start, input_complete_t flags)
816 {
817     const char *p, *q;
818 
819     SHOW_C_CTX ("check_is_cd");
820 
821     if ((flags & INPUT_COMPLETE_CD) == 0)
822         return FALSE;
823 
824     /* Skip initial spaces */
825     p = text;
826     q = text + lc_start;
827     while (p < q && p[0] != '\0' && str_isspace (p))
828         str_cnext_char (&p);
829 
830     /* Check if the command is "cd" and the cursor is after it */
831     return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
832 }
833 
834 /* --------------------------------------------------------------------------------------------- */
835 
836 static void
try_complete_commands_prepare(try_complete_automation_state_t * state,char * text,int * lc_start)837 try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
838 {
839     const char *command_separator_chars = ";|&{(`";
840     char *ti;
841 
842     if (*lc_start == 0)
843         ti = text;
844     else
845     {
846         ti = str_get_prev_char (&text[*lc_start]);
847         while (ti > text && whitespace (ti[0]))
848             str_prev_char (&ti);
849     }
850 
851     if (ti == text)
852         state->in_command_position++;
853     else if (strchr (command_separator_chars, ti[0]) != NULL)
854     {
855         state->in_command_position++;
856         if (ti != text)
857         {
858             int this_char, prev_char;
859 
860             /* Handle the two character tokens '>&', '<&', and '>|'.
861                We are not in a command position after one of these. */
862             this_char = ti[0];
863             prev_char = str_get_prev_char (ti)[0];
864 
865             /* Quoted */
866             if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
867                 || (this_char == '|' && prev_char == '>') || (ti != text
868                                                               && str_get_prev_char (ti)[0] == '\\'))
869                 state->in_command_position = 0;
870         }
871     }
872 }
873 
874 /* --------------------------------------------------------------------------------------------- */
875 
876 static void
try_complete_find_start_sign(try_complete_automation_state_t * state)877 try_complete_find_start_sign (try_complete_automation_state_t * state)
878 {
879     if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
880         state->p = strrchr (state->word, '`');
881     if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
882     {
883         state->q = strrchr (state->word, '$');
884 
885         /* don't substitute variable in \$ case */
886         if (strutils_is_char_escaped (state->word, state->q))
887         {
888             /* drop '\\' */
889             str_move (state->q - 1, state->q);
890             /* adjust flags */
891             state->flags &= ~INPUT_COMPLETE_VARIABLES;
892             state->q = NULL;
893         }
894     }
895     if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
896         state->r = strrchr (state->word, '@');
897     if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
898     {
899         if (state->q > state->p)
900             state->p = str_get_next_char (state->q);
901         state->q = NULL;
902     }
903 }
904 
905 /* --------------------------------------------------------------------------------------------- */
906 
907 static char **
try_complete_all_possible(try_complete_automation_state_t * state,char * text,int * lc_start)908 try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
909 {
910     char **matches = NULL;
911 
912     if (state->in_command_position != 0)
913     {
914         SHOW_C_CTX ("try_complete:cmd_subst");
915         matches =
916             completion_matches (state->word, command_completion_function,
917                                 state->flags & (~INPUT_COMPLETE_FILENAMES));
918     }
919     else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
920     {
921         if (state->is_cd)
922             state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
923         SHOW_C_CTX ("try_complete:filename_subst_1");
924         matches = completion_matches (state->word, filename_completion_function, state->flags);
925 
926         if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
927         {
928             state->q = text + *lc_start;
929             for (state->p = text;
930                  *state->p != '\0' && state->p < state->q && whitespace (*state->p);
931                  str_next_char (&state->p))
932                 ;
933             if (strncmp (state->p, "cd", 2) == 0)
934                 for (state->p += 2;
935                      *state->p != '\0' && state->p < state->q && whitespace (*state->p);
936                      str_next_char (&state->p))
937                     ;
938             if (state->p == state->q)
939             {
940                 char *cdpath_ref, *cdpath;
941                 char c;
942 
943                 cdpath_ref = g_strdup (getenv ("CDPATH"));
944                 cdpath = cdpath_ref;
945                 c = (cdpath == NULL) ? '\0' : ':';
946 
947                 while (matches == NULL && c == ':')
948                 {
949                     char *s;
950 
951                     s = strchr (cdpath, ':');
952                     /* cppcheck-suppress nullPointer */
953                     if (s == NULL)
954                         s = strchr (cdpath, '\0');
955                     c = *s;
956                     *s = '\0';
957                     if (*cdpath != '\0')
958                     {
959                         state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
960                         SHOW_C_CTX ("try_complete:filename_subst_2");
961                         matches =
962                             completion_matches (state->r, filename_completion_function,
963                                                 state->flags);
964                         g_free (state->r);
965                     }
966                     *s = c;
967                     cdpath = str_get_next_char (s);
968                 }
969                 g_free (cdpath_ref);
970             }
971         }
972     }
973     return matches;
974 }
975 
976 /* --------------------------------------------------------------------------------------------- */
977 
978 static gboolean
insert_text(WInput * in,char * text,ssize_t size)979 insert_text (WInput * in, char *text, ssize_t size)
980 {
981     size_t text_len;
982     int buff_len;
983 
984     text_len = strlen (text);
985     buff_len = str_length (in->buffer);
986     size = MIN (size, (ssize_t) text_len) + start - end;
987     if (strlen (in->buffer) + size >= (size_t) in->current_max_size)
988     {
989         /* Expand the buffer */
990         char *narea;
991         Widget *w = WIDGET (in);
992 
993         narea = g_try_realloc (in->buffer, in->current_max_size + size + w->cols);
994         if (narea != NULL)
995         {
996             in->buffer = narea;
997             in->current_max_size += size + w->cols;
998         }
999     }
1000     if (strlen (in->buffer) + 1 < (size_t) in->current_max_size)
1001     {
1002         if (size != 0)
1003             memmove (in->buffer + end + size, in->buffer + end, strlen (&in->buffer[end]) + 1);
1004         memmove (in->buffer + start, text, size - (start - end));
1005         in->point += str_length (in->buffer) - buff_len;
1006         input_update (in, TRUE);
1007         end += size;
1008     }
1009 
1010     return size != 0;
1011 }
1012 
1013 /* --------------------------------------------------------------------------------------------- */
1014 
1015 static cb_ret_t
complete_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)1016 complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
1017 {
1018     static int bl = 0;
1019 
1020     WGroup *g = GROUP (w);
1021     WDialog *h = DIALOG (w);
1022 
1023     switch (msg)
1024     {
1025     case MSG_KEY:
1026         switch (parm)
1027         {
1028         case KEY_LEFT:
1029         case KEY_RIGHT:
1030             bl = 0;
1031             h->ret_value = 0;
1032             dlg_stop (h);
1033             return MSG_HANDLED;
1034 
1035         case KEY_BACKSPACE:
1036             bl = 0;
1037             /* exit from completion list if input line is empty */
1038             if (end == 0)
1039             {
1040                 h->ret_value = 0;
1041                 dlg_stop (h);
1042             }
1043             /* Refill the list box and start again */
1044             else if (end == min_end)
1045             {
1046                 end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1047                 input_handle_char (input, parm);
1048                 h->ret_value = B_USER;
1049                 dlg_stop (h);
1050             }
1051             else
1052             {
1053                 int new_end;
1054                 int i;
1055                 GList *e;
1056 
1057                 new_end = str_get_prev_char (&input->buffer[end]) - input->buffer;
1058 
1059                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1060                      e != NULL; i++, e = g_list_next (e))
1061                 {
1062                     WLEntry *le = LENTRY (e->data);
1063 
1064                     if (strncmp (input->buffer + start, le->text, new_end - start) == 0)
1065                     {
1066                         listbox_select_entry (LISTBOX (g->current->data), i);
1067                         end = new_end;
1068                         input_handle_char (input, parm);
1069                         widget_draw (WIDGET (g->current->data));
1070                         break;
1071                     }
1072                 }
1073             }
1074             return MSG_HANDLED;
1075 
1076         default:
1077             if (parm < 32 || parm > 255)
1078             {
1079                 bl = 0;
1080                 if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
1081                     return MSG_NOT_HANDLED;
1082 
1083                 if (end == min_end)
1084                     return MSG_HANDLED;
1085 
1086                 /* This means we want to refill the list box and start again */
1087                 h->ret_value = B_USER;
1088                 dlg_stop (h);
1089             }
1090             else
1091             {
1092                 static char buff[MB_LEN_MAX] = "";
1093                 GList *e;
1094                 int i;
1095                 int need_redraw = 0;
1096                 int low = 4096;
1097                 char *last_text = NULL;
1098 
1099                 buff[bl++] = (char) parm;
1100                 buff[bl] = '\0';
1101 
1102                 switch (str_is_valid_char (buff, bl))
1103                 {
1104                 case -1:
1105                     bl = 0;
1106                     MC_FALLTHROUGH;
1107                 case -2:
1108                     return MSG_HANDLED;
1109                 default:
1110                     break;
1111                 }
1112 
1113                 for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
1114                      e != NULL; i++, e = g_list_next (e))
1115                 {
1116                     WLEntry *le = LENTRY (e->data);
1117 
1118                     if (strncmp (input->buffer + start, le->text, end - start) == 0
1119                         && strncmp (&le->text[end - start], buff, bl) == 0)
1120                     {
1121                         if (need_redraw == 0)
1122                         {
1123                             need_redraw = 1;
1124                             listbox_select_entry (LISTBOX (g->current->data), i);
1125                             last_text = le->text;
1126                         }
1127                         else
1128                         {
1129                             char *si, *sl;
1130                             int si_num = 0;
1131                             int sl_num = 0;
1132 
1133                             /* count symbols between start and end */
1134                             for (si = le->text + start; si < le->text + end;
1135                                  str_next_char (&si), si_num++)
1136                                 ;
1137                             for (sl = last_text + start; sl < last_text + end;
1138                                  str_next_char (&sl), sl_num++)
1139                                 ;
1140 
1141                             /* pointers to next symbols */
1142                             si = &le->text[str_offset_to_pos (le->text, ++si_num)];
1143                             sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
1144 
1145                             while (si[0] != '\0' && sl[0] != '\0')
1146                             {
1147                                 char *nexti, *nextl;
1148 
1149                                 nexti = str_get_next_char (si);
1150                                 nextl = str_get_next_char (sl);
1151 
1152                                 if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
1153                                     break;
1154 
1155                                 si = nexti;
1156                                 sl = nextl;
1157 
1158                                 si_num++;
1159                             }
1160 
1161                             last_text = le->text;
1162 
1163                             si = &last_text[str_offset_to_pos (last_text, si_num)];
1164                             if (low > si - last_text)
1165                                 low = si - last_text;
1166 
1167                             need_redraw = 2;
1168                         }
1169                     }
1170                 }
1171 
1172                 if (need_redraw == 2)
1173                 {
1174                     insert_text (input, last_text, low);
1175                     widget_draw (WIDGET (g->current->data));
1176                 }
1177                 else if (need_redraw == 1)
1178                 {
1179                     h->ret_value = B_ENTER;
1180                     dlg_stop (h);
1181                 }
1182                 bl = 0;
1183             }
1184         }
1185         return MSG_HANDLED;
1186 
1187     default:
1188         return dlg_default_callback (w, sender, msg, parm, data);
1189     }
1190 }
1191 
1192 /* --------------------------------------------------------------------------------------------- */
1193 
1194 /** Returns TRUE if the user would like to see us again */
1195 static gboolean
complete_engine(WInput * in,int what_to_do)1196 complete_engine (WInput * in, int what_to_do)
1197 {
1198     if (in->completions != NULL && str_offset_to_pos (in->buffer, in->point) != end)
1199         input_complete_free (in);
1200 
1201     if (in->completions == NULL)
1202         complete_engine_fill_completions (in);
1203 
1204     if (in->completions == NULL)
1205         tty_beep ();
1206     else
1207     {
1208         if ((what_to_do & DO_INSERTION) != 0
1209             || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
1210         {
1211             char *lc_complete = in->completions[0];
1212 
1213             if (!insert_text (in, lc_complete, strlen (lc_complete)) || in->completions[1] != NULL)
1214                 tty_beep ();
1215             else
1216                 input_complete_free (in);
1217         }
1218 
1219         if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
1220         {
1221             int maxlen = 0, count = 0, i;
1222             int x, y, w, h;
1223             int start_x, start_y;
1224             char **p, *q;
1225             WDialog *complete_dlg;
1226             WListbox *complete_list;
1227 
1228             for (p = in->completions + 1; *p != NULL; count++, p++)
1229             {
1230                 i = str_term_width1 (*p);
1231                 if (i > maxlen)
1232                     maxlen = i;
1233             }
1234 
1235             start_x = WIDGET (in)->x;
1236             start_y = WIDGET (in)->y;
1237             if (start_y - 2 >= count)
1238             {
1239                 y = start_y - 2 - count;
1240                 h = 2 + count;
1241             }
1242             else if (start_y >= LINES - start_y - 1)
1243             {
1244                 y = 0;
1245                 h = start_y;
1246             }
1247             else
1248             {
1249                 y = start_y + 1;
1250                 h = LINES - start_y - 1;
1251             }
1252             x = start - in->term_first_shown - 2 + start_x;
1253             w = maxlen + 4;
1254             if (x + w > COLS)
1255                 x = COLS - w;
1256             if (x < 0)
1257                 x = 0;
1258             if (x + w > COLS)
1259                 w = COLS;
1260 
1261             input = in;
1262             min_end = end;
1263             complete_height = h;
1264             complete_width = w;
1265 
1266             complete_dlg =
1267                 dlg_create (TRUE, y, x, complete_height, complete_width, WPOS_KEEP_DEFAULT, TRUE,
1268                             dialog_colors, complete_callback, NULL, "[Completion]", NULL);
1269             complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
1270             group_add_widget (GROUP (complete_dlg), complete_list);
1271 
1272             for (p = in->completions + 1; *p != NULL; p++)
1273                 listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
1274 
1275             i = dlg_run (complete_dlg);
1276             q = NULL;
1277             if (i == B_ENTER)
1278             {
1279                 listbox_get_current (complete_list, &q, NULL);
1280                 if (q != NULL)
1281                     insert_text (in, q, strlen (q));
1282             }
1283             if (q != NULL || end != min_end)
1284                 input_complete_free (in);
1285             widget_destroy (WIDGET (complete_dlg));
1286 
1287             /* B_USER if user wants to start over again */
1288             return (i == B_USER);
1289         }
1290     }
1291 
1292     return FALSE;
1293 }
1294 
1295 /* --------------------------------------------------------------------------------------------- */
1296 /*** public functions ****************************************************************************/
1297 /* --------------------------------------------------------------------------------------------- */
1298 
1299 /** Returns an array of matches, or NULL if none. */
1300 char **
try_complete(char * text,int * lc_start,int * lc_end,input_complete_t flags)1301 try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
1302 {
1303     try_complete_automation_state_t state;
1304     char **matches = NULL;
1305 
1306     memset (&state, 0, sizeof (state));
1307     state.flags = flags;
1308 
1309     SHOW_C_CTX ("try_complete");
1310     state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
1311 
1312     state.is_cd = check_is_cd (text, *lc_start, state.flags);
1313 
1314     /* Determine if this could be a command word. It is if it appears at
1315        the start of the line (ignoring preceding whitespace), or if it
1316        appears after a character that separates commands. And we have to
1317        be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
1318     if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
1319         try_complete_commands_prepare (&state, text, lc_start);
1320 
1321     try_complete_find_start_sign (&state);
1322 
1323     /* Command substitution? */
1324     if (state.p > state.q && state.p > state.r)
1325     {
1326         SHOW_C_CTX ("try_complete:cmd_backq_subst");
1327         matches = completion_matches (str_cget_next_char (state.p),
1328                                       command_completion_function,
1329                                       state.flags & (~INPUT_COMPLETE_FILENAMES));
1330         if (matches != NULL)
1331             *lc_start += str_get_next_char (state.p) - state.word;
1332     }
1333 
1334     /* Variable name? */
1335     else if (state.q > state.p && state.q > state.r)
1336     {
1337         SHOW_C_CTX ("try_complete:var_subst");
1338         matches = completion_matches (state.q, variable_completion_function, state.flags);
1339         if (matches != NULL)
1340             *lc_start += state.q - state.word;
1341     }
1342 
1343     /* Starts with '@', then look through the known hostnames for
1344        completion first. */
1345     else if (state.r > state.p && state.r > state.q)
1346     {
1347         SHOW_C_CTX ("try_complete:host_subst");
1348         matches = completion_matches (state.r, hostname_completion_function, state.flags);
1349         if (matches != NULL)
1350             *lc_start += state.r - state.word;
1351     }
1352 
1353     /* Starts with '~' and there is no slash in the word, then
1354        try completing this word as a username. */
1355     if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
1356         && strchr (state.word, PATH_SEP) == NULL)
1357     {
1358         SHOW_C_CTX ("try_complete:user_subst");
1359         matches = completion_matches (state.word, username_completion_function, state.flags);
1360     }
1361 
1362     /* If this word is in a command position, then
1363        complete over possible command names, including aliases, functions,
1364        and command names. */
1365     if (matches == NULL)
1366         matches = try_complete_all_possible (&state, text, lc_start);
1367 
1368     /* And finally if nothing found, try complete directory name */
1369     if (matches == NULL)
1370     {
1371         state.in_command_position = 0;
1372         matches = try_complete_all_possible (&state, text, lc_start);
1373     }
1374 
1375     g_free (state.word);
1376 
1377     if (matches != NULL &&
1378         (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
1379         (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
1380     {
1381         /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
1382         char **m;
1383 
1384         for (m = matches; *m != NULL; m++)
1385         {
1386             char *p;
1387 
1388             p = *m;
1389             *m = strutils_shell_escape (*m);
1390             g_free (p);
1391         }
1392     }
1393 
1394     return matches;
1395 }
1396 
1397 /* --------------------------------------------------------------------------------------------- */
1398 
1399 void
complete_engine_fill_completions(WInput * in)1400 complete_engine_fill_completions (WInput * in)
1401 {
1402     char *s;
1403     const char *word_separators;
1404 
1405     word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
1406 
1407     end = str_offset_to_pos (in->buffer, in->point);
1408 
1409     s = in->buffer;
1410     if (in->point != 0)
1411     {
1412         /* get symbol before in->point */
1413         size_t i;
1414 
1415         for (i = in->point - 1; i > 0; i--)
1416             str_next_char (&s);
1417     }
1418 
1419     for (; s >= in->buffer; str_prev_char (&s))
1420     {
1421         start = s - in->buffer;
1422         if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer, s))
1423             break;
1424     }
1425 
1426     if (start < end)
1427     {
1428         str_next_char (&s);
1429         start = s - in->buffer;
1430     }
1431 
1432     in->completions = try_complete (in->buffer, &start, &end, in->completion_flags);
1433 }
1434 
1435 /* --------------------------------------------------------------------------------------------- */
1436 
1437 /* declared in lib/widget/input.h */
1438 void
input_complete(WInput * in)1439 input_complete (WInput * in)
1440 {
1441     int engine_flags;
1442 
1443     if (!str_is_valid_string (in->buffer))
1444         return;
1445 
1446     if (in->completions != NULL)
1447         engine_flags = DO_QUERY;
1448     else
1449     {
1450         engine_flags = DO_INSERTION;
1451 
1452         if (mc_global.widget.show_all_if_ambiguous)
1453             engine_flags |= DO_QUERY;
1454     }
1455 
1456     while (complete_engine (in, engine_flags))
1457         ;
1458 }
1459 
1460 /* --------------------------------------------------------------------------------------------- */
1461 
1462 void
input_complete_free(WInput * in)1463 input_complete_free (WInput * in)
1464 {
1465     g_strfreev (in->completions);
1466     in->completions = NULL;
1467 }
1468 
1469 /* --------------------------------------------------------------------------------------------- */
1470