1 /*
2    User Menu implementation
3 
4    Copyright (C) 1994-2021
5    Free Software Foundation, Inc.
6 
7    Written by:
8    Slava Zanko <slavazanko@gmail.com>, 2013
9    Andrew Borodin <aborodin@vmail.ru>, 2013
10 
11    This file is part of the Midnight Commander.
12 
13    The Midnight Commander is free software: you can redistribute it
14    and/or modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation, either version 3 of the License,
16    or (at your option) any later version.
17 
18    The Midnight Commander is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22 
23    You should have received a copy of the GNU General Public License
24    along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 /** \file usermenu.c
28  *  \brief Source: user menu implementation
29  */
30 
31 #include <config.h>
32 
33 #include <ctype.h>
34 #include <errno.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 
39 #include "lib/global.h"
40 #include "lib/fileloc.h"
41 #include "lib/tty/tty.h"
42 #include "lib/skin.h"
43 #include "lib/search.h"
44 #include "lib/vfs/vfs.h"
45 #include "lib/strutil.h"
46 #include "lib/util.h"
47 #include "lib/widget.h"
48 
49 #include "src/editor/edit.h"    /* WEdit, BLOCK_FILE */
50 #include "src/viewer/mcviewer.h"        /* for default_* externs */
51 
52 #include "src/execute.h"
53 #include "src/setup.h"
54 #include "src/history.h"
55 
56 #include "src/filemanager/dir.h"
57 #include "src/filemanager/filemanager.h"
58 #include "src/filemanager/layout.h"
59 
60 #include "usermenu.h"
61 
62 /*** global variables ****************************************************************************/
63 
64 /*** file scope macro definitions ****************************************************************/
65 
66 #define MAX_ENTRIES 16
67 #define MAX_ENTRY_LEN 60
68 
69 /*** file scope type declarations ****************************************************************/
70 
71 /*** file scope variables ************************************************************************/
72 
73 static gboolean debug_flag = FALSE;
74 static gboolean debug_error = FALSE;
75 static char *menu = NULL;
76 
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
79 
80 /** strip file's extension */
81 static char *
strip_ext(char * ss)82 strip_ext (char *ss)
83 {
84     char *s = ss;
85     char *e = NULL;
86 
87     while (*s != '\0')
88     {
89         if (*s == '.')
90             e = s;
91         if (IS_PATH_SEP (*s) && e != NULL)
92             e = NULL;           /* '.' in *directory* name */
93         s++;
94     }
95     if (e != NULL)
96         *e = '\0';
97     return ss;
98 }
99 
100 /* --------------------------------------------------------------------------------------------- */
101 /**
102  * Check for the "shell_patterns" directive.  If it's found and valid,
103  * interpret it and move the pointer past the directive.  Return the
104  * current pointer.
105  */
106 
107 static char *
check_patterns(char * p)108 check_patterns (char *p)
109 {
110     static const char def_name[] = "shell_patterns=";
111     char *p0 = p;
112 
113     if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
114         return p0;
115 
116     p += sizeof (def_name) - 1;
117     if (*p == '1')
118         easy_patterns = TRUE;
119     else if (*p == '0')
120         easy_patterns = FALSE;
121     else
122         return p0;
123 
124     /* Skip spaces */
125     p++;
126     while (whiteness (*p))
127         p++;
128     return p;
129 }
130 
131 /* --------------------------------------------------------------------------------------------- */
132 /** Copies a whitespace separated argument from p to arg. Returns the
133    point after argument. */
134 
135 static char *
extract_arg(char * p,char * arg,int size)136 extract_arg (char *p, char *arg, int size)
137 {
138     while (*p != '\0' && whiteness (*p))
139         p++;
140 
141     /* support quote space .mnu */
142     while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
143     {
144         char *np;
145 
146         np = str_get_next_char (p);
147         if (np - p >= size)
148             break;
149         memcpy (arg, p, np - p);
150         arg += np - p;
151         size -= np - p;
152         p = np;
153     }
154     *arg = '\0';
155     if (*p == '\0' || *p == '\n')
156         str_prev_char (&p);
157     return p;
158 }
159 
160 /* --------------------------------------------------------------------------------------------- */
161 /* Tests whether the selected file in the panel is of any of the types
162    specified in argument. */
163 
164 static gboolean
test_type(WPanel * panel,char * arg)165 test_type (WPanel * panel, char *arg)
166 {
167     int result = 0;             /* False by default */
168     mode_t st_mode = panel->dir.list[panel->selected].st.st_mode;
169 
170     for (; *arg != '\0'; arg++)
171     {
172         switch (*arg)
173         {
174         case 'n':              /* Not a directory */
175             result |= !S_ISDIR (st_mode);
176             break;
177         case 'r':              /* Regular file */
178             result |= S_ISREG (st_mode);
179             break;
180         case 'd':              /* Directory */
181             result |= S_ISDIR (st_mode);
182             break;
183         case 'l':              /* Link */
184             result |= S_ISLNK (st_mode);
185             break;
186         case 'c':              /* Character special */
187             result |= S_ISCHR (st_mode);
188             break;
189         case 'b':              /* Block special */
190             result |= S_ISBLK (st_mode);
191             break;
192         case 'f':              /* Fifo (named pipe) */
193             result |= S_ISFIFO (st_mode);
194             break;
195         case 's':              /* Socket */
196             result |= S_ISSOCK (st_mode);
197             break;
198         case 'x':              /* Executable */
199             result |= (st_mode & 0111) != 0 ? 1 : 0;
200             break;
201         case 't':
202             result |= panel->marked != 0 ? 1 : 0;
203             break;
204         default:
205             debug_error = TRUE;
206             break;
207         }
208     }
209 
210     return (result != 0);
211 }
212 
213 /* --------------------------------------------------------------------------------------------- */
214 /** Calculates the truth value of the next condition starting from
215    p. Returns the point after condition. */
216 
217 static char *
test_condition(const WEdit * edit_widget,char * p,gboolean * condition)218 test_condition (const WEdit * edit_widget, char *p, gboolean * condition)
219 {
220     char arg[256];
221     const mc_search_type_t search_type = easy_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
222 
223     /* Handle one condition */
224     for (; *p != '\n' && *p != '&' && *p != '|'; p++)
225     {
226         WPanel *panel = NULL;
227 
228         /* support quote space .mnu */
229         if ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
230             continue;
231         if (*p >= 'a')
232             panel = current_panel;
233         else if (get_other_type () == view_listing)
234             panel = other_panel;
235 
236         *p |= 0x20;
237 
238         switch (*p++)
239         {
240         case '!':
241             p = test_condition (edit_widget, p, condition);
242             *condition = !*condition;
243             str_prev_char (&p);
244             break;
245         case 'f':              /* file name pattern */
246             p = extract_arg (p, arg, sizeof (arg));
247 #ifdef USE_INTERNAL_EDIT
248             if (edit_widget != NULL)
249             {
250                 const char *edit_filename;
251 
252                 edit_filename = edit_get_file_name (edit_widget);
253                 *condition = mc_search (arg, DEFAULT_CHARSET, edit_filename, search_type);
254             }
255             else
256 #endif
257                 *condition = panel != NULL &&
258                     mc_search (arg, DEFAULT_CHARSET, panel->dir.list[panel->selected].fname->str,
259                                search_type);
260             break;
261         case 'y':              /* syntax pattern */
262 #ifdef USE_INTERNAL_EDIT
263             if (edit_widget != NULL)
264             {
265                 const char *syntax_type;
266 
267                 syntax_type = edit_get_syntax_type (edit_widget);
268                 if (syntax_type != NULL)
269                 {
270                     p = extract_arg (p, arg, sizeof (arg));
271                     *condition = mc_search (arg, DEFAULT_CHARSET, syntax_type, MC_SEARCH_T_NORMAL);
272                 }
273             }
274 #endif
275             break;
276         case 'd':
277             p = extract_arg (p, arg, sizeof (arg));
278             *condition = panel != NULL
279                 && mc_search (arg, DEFAULT_CHARSET, vfs_path_as_str (panel->cwd_vpath),
280                               search_type);
281             break;
282         case 't':
283             p = extract_arg (p, arg, sizeof (arg));
284             *condition = panel != NULL && test_type (panel, arg);
285             break;
286         case 'x':              /* executable */
287             {
288                 struct stat status;
289 
290                 p = extract_arg (p, arg, sizeof (arg));
291                 *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
292                 break;
293             }
294         default:
295             debug_error = TRUE;
296             break;
297         }                       /* switch */
298     }                           /* while */
299     return p;
300 }
301 
302 /* --------------------------------------------------------------------------------------------- */
303 /** General purpose condition debug output handler */
304 
305 static void
debug_out(char * start,char * end,gboolean condition)306 debug_out (char *start, char *end, gboolean condition)
307 {
308     static char *msg = NULL;
309 
310     if (start == NULL && end == NULL)
311     {
312         /* Show output */
313         if (debug_flag && msg != NULL)
314         {
315             size_t len;
316 
317             len = strlen (msg);
318             if (len != 0)
319                 msg[len - 1] = '\0';
320             message (D_NORMAL, _("Debug"), "%s", msg);
321 
322         }
323         debug_flag = FALSE;
324         MC_PTR_FREE (msg);
325     }
326     else
327     {
328         const char *type;
329         char *p;
330 
331         /* Save debug info for later output */
332         if (!debug_flag)
333             return;
334         /* Save the result of the condition */
335         if (debug_error)
336         {
337             type = _("ERROR:");
338             debug_error = FALSE;
339         }
340         else if (condition)
341             type = _("True:");
342         else
343             type = _("False:");
344         /* This is for debugging, don't need to be super efficient.  */
345         if (end == NULL)
346             p = g_strdup_printf ("%s %s %c \n", msg ? msg : "", type, *start);
347         else
348             p = g_strdup_printf ("%s %s %.*s \n", msg ? msg : "", type, (int) (end - start), start);
349         g_free (msg);
350         msg = p;
351     }
352 }
353 
354 /* --------------------------------------------------------------------------------------------- */
355 /** Calculates the truth value of one lineful of conditions. Returns
356    the point just before the end of line. */
357 
358 static char *
test_line(const WEdit * edit_widget,char * p,gboolean * result)359 test_line (const WEdit * edit_widget, char *p, gboolean * result)
360 {
361     char operator;
362 
363     /* Repeat till end of line */
364     while (*p != '\0' && *p != '\n')
365     {
366         char *debug_start, *debug_end;
367         gboolean condition = TRUE;
368 
369         /* support quote space .mnu */
370         while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
371             p++;
372         if (*p == '\0' || *p == '\n')
373             break;
374         operator = *p++;
375         if (*p == '?')
376         {
377             debug_flag = TRUE;
378             p++;
379         }
380         /* support quote space .mnu */
381         while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
382             p++;
383         if (*p == '\0' || *p == '\n')
384             break;
385 
386         debug_start = p;
387         p = test_condition (edit_widget, p, &condition);
388         debug_end = p;
389         /* Add one debug statement */
390         debug_out (debug_start, debug_end, condition);
391 
392         switch (operator)
393         {
394         case '+':
395         case '=':
396             /* Assignment */
397             *result = condition;
398             break;
399         case '&':              /* Logical and */
400             *result = *result && condition;
401             break;
402         case '|':              /* Logical or */
403             *result = *result || condition;
404             break;
405         default:
406             debug_error = TRUE;
407             break;
408         }                       /* switch */
409         /* Add one debug statement */
410         debug_out (&operator, NULL, *result);
411 
412     }                           /* while (*p != '\n') */
413     /* Report debug message */
414     debug_out (NULL, NULL, TRUE);
415 
416     if (*p == '\0' || *p == '\n')
417         str_prev_char (&p);
418     return p;
419 }
420 
421 /* --------------------------------------------------------------------------------------------- */
422 /** FIXME: recode this routine on version 3.0, it could be cleaner */
423 
424 static void
execute_menu_command(const WEdit * edit_widget,const char * commands,gboolean show_prompt)425 execute_menu_command (const WEdit * edit_widget, const char *commands, gboolean show_prompt)
426 {
427     FILE *cmd_file;
428     int cmd_file_fd;
429     gboolean expand_prefix_found = FALSE;
430     char *parameter = NULL;
431     gboolean do_quote = FALSE;
432     char lc_prompt[80];
433     int col;
434     vfs_path_t *file_name_vpath;
435     gboolean run_view = FALSE;
436     char *cmd;
437 
438     /* Skip menu entry title line */
439     commands = strchr (commands, '\n');
440     if (commands == NULL)
441         return;
442 
443     cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
444 
445     if (cmd_file_fd == -1)
446     {
447         message (D_ERROR, MSG_ERROR, _("Cannot create temporary command file\n%s"),
448                  unix_error_string (errno));
449         return;
450     }
451 
452     cmd_file = fdopen (cmd_file_fd, "w");
453     fputs ("#! /bin/sh\n", cmd_file);
454     commands++;
455 
456     for (col = 0; *commands != '\0'; commands++)
457     {
458         if (col == 0)
459         {
460             if (!whitespace (*commands))
461                 break;
462             while (whitespace (*commands))
463                 commands++;
464             if (*commands == '\0')
465                 break;
466         }
467         col++;
468         if (*commands == '\n')
469             col = 0;
470         if (parameter != NULL)
471         {
472             if (*commands == '}')
473             {
474                 *parameter = '\0';
475                 parameter =
476                     input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
477                                   INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD |
478                                   INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_VARIABLES |
479                                   INPUT_COMPLETE_USERNAMES);
480                 if (parameter == NULL || *parameter == '\0')
481                 {
482                     /* User canceled */
483                     g_free (parameter);
484                     fclose (cmd_file);
485                     mc_unlink (file_name_vpath);
486                     vfs_path_free (file_name_vpath, TRUE);
487                     return;
488                 }
489                 if (do_quote)
490                 {
491                     char *tmp;
492 
493                     tmp = name_quote (parameter, FALSE);
494                     fputs (tmp, cmd_file);
495                     g_free (tmp);
496                 }
497                 else
498                     fputs (parameter, cmd_file);
499 
500                 MC_PTR_FREE (parameter);
501             }
502             else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
503                 *parameter++ = *commands;
504         }
505         else if (expand_prefix_found)
506         {
507             expand_prefix_found = FALSE;
508             if (g_ascii_isdigit ((gchar) * commands))
509             {
510                 do_quote = (atoi (commands) != 0);
511                 while (g_ascii_isdigit ((gchar) * commands))
512                     commands++;
513             }
514             if (*commands == '{')
515                 parameter = lc_prompt;
516             else
517             {
518                 char *text;
519 
520                 text = expand_format (edit_widget, *commands, do_quote);
521                 fputs (text, cmd_file);
522                 g_free (text);
523             }
524         }
525         else if (*commands == '%')
526         {
527             int i;
528 
529             i = check_format_view (commands + 1);
530             if (i != 0)
531             {
532                 commands += i;
533                 run_view = TRUE;
534             }
535             else
536             {
537                 do_quote = TRUE;        /* Default: Quote expanded macro */
538                 expand_prefix_found = TRUE;
539             }
540         }
541         else
542             fputc (*commands, cmd_file);
543     }
544 
545     fclose (cmd_file);
546     mc_chmod (file_name_vpath, S_IRWXU);
547 
548     /* Execute the command indirectly to allow execution even on no-exec filesystems. */
549     cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
550 
551     if (run_view)
552     {
553         mcview_viewer (cmd, NULL, 0, 0, 0);
554         dialog_switch_process_pending ();
555     }
556     else if (show_prompt)
557         shell_execute (cmd, EXECUTE_HIDE);
558     else
559     {
560         gboolean ok;
561 
562         /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
563          * to work as expected, instead of being ignored. */
564         tty_reset_shell_mode ();
565 
566         ok = (system (cmd) != -1);
567 
568         /* Restore terminal configuration. */
569         tty_raw_mode ();
570 
571         /* Redraw the original screen's contents. */
572         tty_clear_screen ();
573         repaint_screen ();
574 
575         if (!ok)
576             message (D_ERROR, MSG_ERROR, "%s", _("Error calling program"));
577     }
578 
579     g_free (cmd);
580 
581     mc_unlink (file_name_vpath);
582     vfs_path_free (file_name_vpath, TRUE);
583 }
584 
585 /* --------------------------------------------------------------------------------------------- */
586 /**
587  **     Check owner of the menu file. Using menu file is allowed, if
588  **     owner of the menu is root or the actual user. In either case
589  **     file should not be group and word-writable.
590  **
591  **     Q. Should we apply this routine to system and home menu (and .ext files)?
592  */
593 
594 static gboolean
menu_file_own(char * path)595 menu_file_own (char *path)
596 {
597     struct stat st;
598 
599     if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
600         && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
601         return TRUE;
602 
603     if (verbose)
604         message (D_NORMAL, _("Warning -- ignoring file"),
605                  _("File %s is not owned by root or you or is world writable.\n"
606                    "Using it may compromise your security"), path);
607 
608     return FALSE;
609 }
610 
611 /* --------------------------------------------------------------------------------------------- */
612 /*** public functions ****************************************************************************/
613 /* --------------------------------------------------------------------------------------------- */
614 
615 /* Formats defined:
616    %%  The % character
617    %f  The current file in the active panel (if non-local vfs, file will be copied locally
618    and %f will be full path to it) or the opened file in the internal editor.
619    %p  Likewise.
620    %d  The current working directory
621    %s  "Selected files"; the tagged files if any, otherwise the current file
622    %t  Tagged files
623    %u  Tagged files (and they are untagged on return from expand_format)
624    %view Runs the commands and pipes standard output to the view command.
625    If %view is immediately followed by '{', recognize keywords
626    ascii, hex, nroff and unform
627 
628    If the format letter is in uppercase, it refers to the other panel.
629 
630    With a number followed the % character you can turn quoting on (default)
631    and off. For example:
632    %f    quote expanded macro
633    %1f   ditto
634    %0f   don't quote expanded macro
635 
636    expand_format returns a memory block that must be free()d.
637  */
638 
639 /* Returns how many characters we should advance if %view was found */
640 int
check_format_view(const char * p)641 check_format_view (const char *p)
642 {
643     const char *q = p;
644 
645     if (strncmp (p, "view", 4) == 0)
646     {
647         q += 4;
648         if (*q == '{')
649         {
650             for (q++; *q != '\0' && *q != '}'; q++)
651             {
652                 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
653                 {
654                     mcview_global_flags.hex = FALSE;
655                     q += 4;
656                 }
657                 else if (strncmp (q, "hex", 3) == 0)
658                 {
659                     mcview_global_flags.hex = TRUE;
660                     q += 2;
661                 }
662                 else if (strncmp (q, "nroff", 5) == 0)
663                 {
664                     mcview_global_flags.nroff = TRUE;
665                     q += 4;
666                 }
667                 else if (strncmp (q, "unform", 6) == 0)
668                 {
669                     mcview_global_flags.nroff = FALSE;
670                     q += 5;
671                 }
672             }
673             if (*q == '}')
674                 q++;
675         }
676         return q - p;
677     }
678     return 0;
679 }
680 
681 /* --------------------------------------------------------------------------------------------- */
682 
683 int
check_format_cd(const char * p)684 check_format_cd (const char *p)
685 {
686     return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
687 }
688 
689 /* --------------------------------------------------------------------------------------------- */
690 /* Check if p has a "^var\{var-name\}" */
691 /* Returns the number of skipped characters (zero on not found) */
692 /* V will be set to the expanded variable name */
693 
694 int
check_format_var(const char * p,char ** v)695 check_format_var (const char *p, char **v)
696 {
697     *v = NULL;
698 
699     if (strncmp (p, "var{", 4) == 0)
700     {
701         const char *q = p;
702         const char *dots = NULL;
703         const char *value;
704         char *var_name;
705 
706         for (q += 4; *q != '\0' && *q != '}'; q++)
707         {
708             if (*q == ':')
709                 dots = q + 1;
710         }
711         if (*q == '\0')
712             return 0;
713 
714         if (dots == NULL || dots == q + 5)
715         {
716             message (D_ERROR,
717                      _("Format error on file Extensions File"),
718                      !dots ? _("The %%var macro has no default")
719                      : _("The %%var macro has no variable"));
720             return 0;
721         }
722 
723         /* Copy the variable name */
724         var_name = g_strndup (p + 4, dots - 2 - (p + 3));
725         value = getenv (var_name);
726         g_free (var_name);
727 
728         if (value != NULL)
729             *v = g_strdup (value);
730         else
731             *v = g_strndup (dots, q - dots);
732 
733         return q - p;
734     }
735     return 0;
736 }
737 
738 /* --------------------------------------------------------------------------------------------- */
739 
740 char *
expand_format(const WEdit * edit_widget,char c,gboolean do_quote)741 expand_format (const WEdit * edit_widget, char c, gboolean do_quote)
742 {
743     WPanel *panel = NULL;
744     char *(*quote_func) (const char *, gboolean);
745     const char *fname = NULL;
746     char *result;
747     char c_lc;
748 
749 #ifndef USE_INTERNAL_EDIT
750     (void) edit_widget;
751 #endif
752 
753     if (c == '%')
754         return g_strdup ("%");
755 
756     switch (mc_global.mc_run_mode)
757     {
758     case MC_RUN_FULL:
759 #ifdef USE_INTERNAL_EDIT
760         if (edit_widget != NULL)
761             fname = edit_get_file_name (edit_widget);
762         else
763 #endif
764         {
765             if (g_ascii_islower ((gchar) c))
766                 panel = current_panel;
767             else
768             {
769                 if (get_other_type () != view_listing)
770                     return g_strdup ("");
771                 panel = other_panel;
772             }
773 
774             fname = panel->dir.list[panel->selected].fname->str;
775         }
776         break;
777 
778 #ifdef USE_INTERNAL_EDIT
779     case MC_RUN_EDITOR:
780         fname = edit_get_file_name (edit_widget);
781         break;
782 #endif
783 
784     default:
785         /* other modes don't use formats */
786         return g_strdup ("");
787     }
788 
789     if (do_quote)
790         quote_func = name_quote;
791     else
792         quote_func = fake_name_quote;
793 
794     c_lc = g_ascii_tolower ((gchar) c);
795 
796     switch (c_lc)
797     {
798     case 'f':
799     case 'p':
800         result = quote_func (fname, FALSE);
801         goto ret;
802     case 'x':
803         result = quote_func (extension (fname), FALSE);
804         goto ret;
805     case 'd':
806         {
807             const char *cwd;
808             char *qstr;
809 
810             if (panel != NULL)
811                 cwd = vfs_path_as_str (panel->cwd_vpath);
812             else
813                 cwd = vfs_get_current_dir ();
814 
815             qstr = quote_func (cwd, FALSE);
816 
817             result = qstr;
818             goto ret;
819         }
820     case 'c':
821 #ifdef USE_INTERNAL_EDIT
822         if (edit_widget != NULL)
823         {
824             result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (edit_widget));
825             goto ret;
826         }
827 #endif
828         break;
829     case 'i':                  /* indent equal number cursor position in line */
830 #ifdef USE_INTERNAL_EDIT
831         if (edit_widget != NULL)
832         {
833             result = g_strnfill (edit_get_curs_col (edit_widget), ' ');
834             goto ret;
835         }
836 #endif
837         break;
838     case 'y':                  /* syntax type */
839 #ifdef USE_INTERNAL_EDIT
840         if (edit_widget != NULL)
841         {
842             const char *syntax_type;
843 
844             syntax_type = edit_get_syntax_type (edit_widget);
845             if (syntax_type != NULL)
846             {
847                 result = g_strdup (syntax_type);
848                 goto ret;
849             }
850         }
851 #endif
852         break;
853     case 'k':                  /* block file name */
854     case 'b':                  /* block file name / strip extension */
855 #ifdef USE_INTERNAL_EDIT
856         if (edit_widget != NULL)
857         {
858             char *file;
859 
860             file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
861             result = quote_func (file, FALSE);
862             g_free (file);
863             goto ret;
864         }
865 #endif
866         if (c_lc == 'b')
867         {
868             result = strip_ext (quote_func (fname, FALSE));
869             goto ret;
870         }
871         break;
872     case 'n':                  /* strip extension in editor */
873 #ifdef USE_INTERNAL_EDIT
874         if (edit_widget != NULL)
875         {
876             result = strip_ext (quote_func (fname, FALSE));
877             goto ret;
878         }
879 #endif
880         break;
881     case 'm':                  /* menu file name */
882         if (menu != NULL)
883         {
884             result = quote_func (menu, FALSE);
885             goto ret;
886         }
887         break;
888     case 's':
889         if (panel == NULL || panel->marked == 0)
890         {
891             result = quote_func (fname, FALSE);
892             goto ret;
893         }
894 
895         MC_FALLTHROUGH;
896 
897     case 't':
898     case 'u':
899         {
900             GString *block;
901             int i;
902 
903             if (panel == NULL)
904             {
905                 result = g_strdup ("");
906                 goto ret;
907             }
908 
909             block = g_string_sized_new (16);
910 
911             for (i = 0; i < panel->dir.len; i++)
912                 if (panel->dir.list[i].f.marked)
913                 {
914                     char *tmp;
915 
916                     tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
917                     g_string_append (block, tmp);
918                     g_string_append_c (block, ' ');
919                     g_free (tmp);
920 
921                     if (c_lc == 'u')
922                         do_file_mark (panel, i, 0);
923                 }
924             result = g_string_free (block, FALSE);
925             goto ret;
926         }                       /* sub case block */
927     default:
928         break;
929     }                           /* switch */
930 
931     result = g_strdup ("% ");
932     result[1] = c;
933   ret:
934     return result;
935 }
936 
937 /* --------------------------------------------------------------------------------------------- */
938 /**
939  * If edit_widget is NULL then we are called from the mc menu,
940  * otherwise we are called from the mcedit menu.
941  */
942 
943 gboolean
user_menu_cmd(const WEdit * edit_widget,const char * menu_file,int selected_entry)944 user_menu_cmd (const WEdit * edit_widget, const char *menu_file, int selected_entry)
945 {
946     char *p;
947     char *data, **entries;
948     int max_cols, menu_lines, menu_limit;
949     int col, i;
950     gboolean accept_entry = TRUE;
951     int selected;
952     gboolean old_patterns;
953     gboolean res = FALSE;
954     gboolean interactive = TRUE;
955 
956     if (!vfs_current_is_local ())
957     {
958         message (D_ERROR, MSG_ERROR, "%s", _("Cannot execute commands on non-local filesystems"));
959         return FALSE;
960     }
961     if (menu_file != NULL)
962         menu = g_strdup (menu_file);
963     else
964         menu = g_strdup (edit_widget != NULL ? EDIT_LOCAL_MENU : MC_LOCAL_MENU);
965     if (!exist_file (menu) || !menu_file_own (menu))
966     {
967         if (menu_file != NULL)
968         {
969             message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu,
970                      unix_error_string (errno));
971             MC_PTR_FREE (menu);
972             return FALSE;
973         }
974 
975         g_free (menu);
976         if (edit_widget != NULL)
977             menu = mc_config_get_full_path (EDIT_HOME_MENU);
978         else
979             menu = mc_config_get_full_path (MC_USERMENU_FILE);
980 
981         if (!exist_file (menu))
982         {
983             g_free (menu);
984             menu =
985                 mc_build_filename (mc_config_get_home_dir (),
986                                    edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
987                                    (char *) NULL);
988             if (!exist_file (menu))
989             {
990                 g_free (menu);
991                 menu =
992                     mc_build_filename (mc_global.sysconfig_dir,
993                                        edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
994                                        (char *) NULL);
995                 if (!exist_file (menu))
996                 {
997                     g_free (menu);
998                     menu =
999                         mc_build_filename (mc_global.share_data_dir,
1000                                            edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
1001                                            (char *) NULL);
1002                 }
1003             }
1004         }
1005     }
1006 
1007     if (!g_file_get_contents (menu, &data, NULL, NULL))
1008     {
1009         message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu, unix_error_string (errno));
1010         MC_PTR_FREE (menu);
1011         return FALSE;
1012     }
1013 
1014     max_cols = 0;
1015     selected = 0;
1016     menu_limit = 0;
1017     entries = NULL;
1018 
1019     /* Parse the menu file */
1020     old_patterns = easy_patterns;
1021     p = check_patterns (data);
1022     for (menu_lines = col = 0; *p != '\0'; str_next_char (&p))
1023     {
1024         if (menu_lines >= menu_limit)
1025         {
1026             char **new_entries;
1027 
1028             menu_limit += MAX_ENTRIES;
1029             new_entries = g_try_realloc (entries, sizeof (new_entries[0]) * menu_limit);
1030             if (new_entries == NULL)
1031                 break;
1032 
1033             entries = new_entries;
1034             new_entries += menu_limit;
1035             while (--new_entries >= &entries[menu_lines])
1036                 *new_entries = NULL;
1037         }
1038 
1039         if (col == 0 && entries[menu_lines] == NULL)
1040             switch (*p)
1041             {
1042             case '#':
1043                 /* do not show prompt if first line of external script is #silent */
1044                 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1045                     interactive = FALSE;
1046                 /* A commented menu entry */
1047                 accept_entry = TRUE;
1048                 break;
1049 
1050             case '+':
1051                 if (*(p + 1) == '=')
1052                 {
1053                     /* Combined adding and default */
1054                     p = test_line (edit_widget, p + 1, &accept_entry);
1055                     if (selected == 0 && accept_entry)
1056                         selected = menu_lines;
1057                 }
1058                 else
1059                 {
1060                     /* A condition for adding the entry */
1061                     p = test_line (edit_widget, p, &accept_entry);
1062                 }
1063                 break;
1064 
1065             case '=':
1066                 if (*(p + 1) == '+')
1067                 {
1068                     /* Combined adding and default */
1069                     p = test_line (edit_widget, p + 1, &accept_entry);
1070                     if (selected == 0 && accept_entry)
1071                         selected = menu_lines;
1072                 }
1073                 else
1074                 {
1075                     /* A condition for making the entry default */
1076                     i = 1;
1077                     p = test_line (edit_widget, p, &i);
1078                     if (selected == 0 && i != 0)
1079                         selected = menu_lines;
1080                 }
1081                 break;
1082 
1083             default:
1084                 if (!whitespace (*p) && str_isprint (p))
1085                 {
1086                     /* A menu entry title line */
1087                     if (accept_entry)
1088                         entries[menu_lines] = p;
1089                     else
1090                         accept_entry = TRUE;
1091                 }
1092                 break;
1093             }
1094 
1095         if (*p == '\n')
1096         {
1097             if (entries[menu_lines] != NULL)
1098             {
1099                 menu_lines++;
1100                 accept_entry = TRUE;
1101             }
1102             max_cols = MAX (max_cols, col);
1103             col = 0;
1104         }
1105         else
1106         {
1107             if (*p == '\t')
1108                 *p = ' ';
1109             col++;
1110         }
1111     }
1112 
1113     if (menu_lines == 0)
1114     {
1115         message (D_ERROR, MSG_ERROR, _("No suitable entries found in %s"), menu);
1116         res = FALSE;
1117     }
1118     else
1119     {
1120         if (selected_entry >= 0)
1121             selected = selected_entry;
1122         else
1123         {
1124             Listbox *listbox;
1125 
1126             max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1127 
1128             /* Create listbox */
1129             listbox = create_listbox_window (menu_lines, max_cols + 2, _("User menu"),
1130                                              "[Edit Menu File]");
1131             /* insert all the items found */
1132             for (i = 0; i < menu_lines; i++)
1133             {
1134                 p = entries[i];
1135                 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1136                                      extract_line (p, p + MAX_ENTRY_LEN), p, FALSE);
1137             }
1138             /* Select the default entry */
1139             listbox_select_entry (listbox->list, selected);
1140 
1141             selected = run_listbox (listbox);
1142         }
1143         if (selected >= 0)
1144         {
1145             execute_menu_command (edit_widget, entries[selected], interactive);
1146             res = TRUE;
1147         }
1148 
1149         do_refresh ();
1150     }
1151 
1152     easy_patterns = old_patterns;
1153     MC_PTR_FREE (menu);
1154     g_free (entries);
1155     g_free (data);
1156     return res;
1157 }
1158 
1159 /* --------------------------------------------------------------------------------------------- */
1160