Manual pages: mcmcdiffmceditmcview

root/src/filemanager/command.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. command_has_unquoted_metacharacters
  2. enter
  3. command_callback
  4. command_new
  5. command_insert

   1 /*
   2    Command line widget.
   3    This widget is derived from the WInput widget, it's used to cope
   4    with all the magic of the command input line, we depend on some
   5    help from the program's callback.
   6 
   7    Copyright (C) 1995-2026
   8    Free Software Foundation, Inc.
   9 
  10    Written by:
  11    Slava Zanko <slavazanko@gmail.com>, 2013
  12    Andrew Borodin <aborodin@vmail.ru>, 2011-2022
  13    Marek Libra <marek.libra@gmail.com>, 2026
  14 
  15    This file is part of the Midnight Commander.
  16 
  17    The Midnight Commander is free software: you can redistribute it
  18    and/or modify it under the terms of the GNU General Public License as
  19    published by the Free Software Foundation, either version 3 of the License,
  20    or (at your option) any later version.
  21 
  22    The Midnight Commander is distributed in the hope that it will be useful,
  23    but WITHOUT ANY WARRANTY; without even the implied warranty of
  24    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25    GNU General Public License for more details.
  26 
  27    You should have received a copy of the GNU General Public License
  28    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  29  */
  30 
  31 /** \file command.c
  32  *  \brief Source: command line widget
  33  */
  34 
  35 #include <config.h>
  36 
  37 #include <stdlib.h>
  38 #include <string.h>
  39 
  40 #include "lib/global.h"
  41 #include "lib/vfs/vfs.h"  // vfs_current_is_local()
  42 #include "lib/skin.h"
  43 #include "lib/util.h"  // whitespace()
  44 #include "lib/widget.h"
  45 
  46 #include "src/setup.h"  // quit
  47 #ifdef ENABLE_SUBSHELL
  48 #include "src/subshell/subshell.h"
  49 #endif
  50 #include "src/execute.h"   // shell_execute()
  51 #include "src/usermenu.h"  // expand_format()
  52 
  53 #include "filemanager.h"  // quiet_quit_cmd(), layout.h
  54 #include "cd.h"           // cd_to()
  55 
  56 #include "command.h"
  57 
  58 /*** global variables ****************************************************************************/
  59 
  60 /* This holds the command line */
  61 WInput *cmdline;
  62 
  63 /*** file scope macro definitions ****************************************************************/
  64 
  65 /*** file scope type declarations ****************************************************************/
  66 
  67 /*** forward declarations (file scope functions) *************************************************/
  68 
  69 #ifdef HAVE_TESTS
  70 MC_TESTABLE gboolean command_has_unquoted_metacharacters (const char *cmd);
  71 #endif
  72 
  73 /*** file scope variables ************************************************************************/
  74 
  75 /* Color styles command line */
  76 static const input_colors_t command_colors = {
  77     [INPUT_COLOR_MAIN] = CORE_COMMANDLINE_COLOR,
  78     [INPUT_COLOR_MARK] = CORE_COMMANDLINE_MARK_COLOR,
  79     [INPUT_COLOR_UNCHANGED] = CORE_COMMANDLINE_COLOR,
  80     [INPUT_COLOR_HISTORY] = CORE_COMMAND_HISTORY_COLOR,
  81 };
  82 
  83 /* --------------------------------------------------------------------------------------------- */
  84 /*** file scope functions ************************************************************************/
  85 /* --------------------------------------------------------------------------------------------- */
  86 /**
  87  * Detect shell syntax that the internal cd handler cannot process.
  88  *
  89  * Returns TRUE when any of the following appears outside of single quotes
  90  * and backslash escaping:
  91  *   - Command separators/operators: ; | &
  92  *   - Command substitution: $( or ` (including inside double quotes,
  93  *     where the shell still expands them)
  94  *
  95  * Note: plain parameter expansion ($VAR, ${VAR}) is intentionally excluded
  96  * because the internal cd handler expands environment variables itself.
  97  *
  98  * Motivation: the internal cd handler cannot expand command substitutions
  99  * nor execute compound commands. When any of these constructs are present,
 100  * the entire command line must be passed to the shell.
 101  *
 102  * Only single quotes and backslash fully suppress detection. Double quotes
 103  * suppress ; | & but NOT $( and ` because the shell expands command
 104  * substitution inside double quotes.
 105  *
 106  * Parsing strategy: forward scan tracking quote state.
 107  * We treat $( and ` as immediate indicators rather than parsing their
 108  * contents, because internal cd cannot handle them regardless
 109  * of what is inside.
 110  */
 111 
 112 MC_TESTABLE gboolean
 113 command_has_unquoted_metacharacters (const char *cmd)
     /* [previous][next][first][last][top][bottom][index][help]  */
 114 {
 115     gboolean in_single_quote = FALSE;
 116     gboolean in_double_quote = FALSE;
 117 
 118     for (const char *p = cmd; *p != '\0'; p++)
 119     {
 120         if (in_single_quote)
 121         {
 122             if (*p == '\'')
 123                 in_single_quote = FALSE;
 124             continue;
 125         }
 126 
 127         if (*p == '\\' && p[1] != '\0')
 128         {
 129             p++;
 130             continue;
 131         }
 132 
 133         if (*p == '`')
 134             return TRUE;
 135 
 136         if (*p == '$' && p[1] == '(')
 137             return TRUE;
 138 
 139         if (in_double_quote)
 140         {
 141             if (*p == '"')
 142                 in_double_quote = FALSE;
 143             continue;
 144         }
 145 
 146         if (*p == '\'')
 147         {
 148             in_single_quote = TRUE;
 149             continue;
 150         }
 151 
 152         if (*p == '"')
 153         {
 154             in_double_quote = TRUE;
 155             continue;
 156         }
 157 
 158         if (*p == ';' || *p == '|' || *p == '&')
 159             return TRUE;
 160     }
 161 
 162     return FALSE;
 163 }
 164 
 165 /* --------------------------------------------------------------------------------------------- */
 166 
 167 /** Handle Enter on the command line
 168  *
 169  * @param lc_cmdline string for handling
 170  * @return MSG_HANDLED on success else MSG_NOT_HANDLED
 171  */
 172 
 173 static cb_ret_t
 174 enter (WInput *lc_cmdline)
     /* [previous][next][first][last][top][bottom][index][help]  */
 175 {
 176     const char *cmd;
 177 
 178     if (!command_prompt)
 179         return MSG_HANDLED;
 180 
 181     cmd = input_get_ctext (lc_cmdline);
 182 
 183     // Any initial whitespace should be removed at this point
 184     while (whiteness (*cmd))
 185         cmd++;
 186 
 187     if (*cmd == '\0')
 188         return MSG_HANDLED;
 189 
 190     if (strncmp (cmd, "cd", 2) == 0 && (cmd[2] == '\0' || whitespace (cmd[2]))
 191         && !command_has_unquoted_metacharacters (cmd))
 192     {
 193         cd_to (cmd + 2);
 194         input_clean (lc_cmdline);
 195         return MSG_HANDLED;
 196     }
 197     else if (strcmp (cmd, "exit") == 0)
 198     {
 199         input_assign_text (lc_cmdline, "");
 200         if (!quiet_quit_cmd (FALSE))
 201             return MSG_NOT_HANDLED;
 202     }
 203     else
 204     {
 205         GString *command;
 206         size_t i;
 207 
 208         if (!vfs_current_is_local ())
 209         {
 210             message (D_ERROR, MSG_ERROR, _ ("Cannot execute commands on non-local filesystems"));
 211             return MSG_NOT_HANDLED;
 212         }
 213 #ifdef ENABLE_SUBSHELL
 214         /* Check this early before we clean command line
 215          * (will be checked again by shell_execute) */
 216         if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
 217         {
 218             message (D_ERROR, MSG_ERROR, _ ("The shell is already running a command"));
 219             return MSG_NOT_HANDLED;
 220         }
 221 #endif
 222         command = g_string_sized_new (32);
 223 
 224         for (i = 0; cmd[i] != '\0'; i++)
 225         {
 226             if (cmd[i] != '%')
 227                 g_string_append_c (command, cmd[i]);
 228             else
 229             {
 230                 GString *s;
 231 
 232                 s = expand_format (NULL, cmd[++i], TRUE);
 233                 if (s != NULL)
 234                 {
 235                     mc_g_string_concat (command, s);
 236                     g_string_free (s, TRUE);
 237                 }
 238             }
 239         }
 240 
 241         input_clean (lc_cmdline);
 242         shell_execute (command->str, 0);
 243         g_string_free (command, TRUE);
 244 
 245 #ifdef ENABLE_SUBSHELL
 246         if ((quit & SUBSHELL_EXIT) != 0)
 247         {
 248             if (quiet_quit_cmd (FALSE))
 249                 return MSG_HANDLED;
 250 
 251             quit = 0;
 252             // restart subshell
 253             if (mc_global.tty.use_subshell)
 254                 init_subshell ();
 255         }
 256 
 257         if (mc_global.tty.use_subshell)
 258             do_load_prompt ();
 259 #endif
 260     }
 261     return MSG_HANDLED;
 262 }
 263 
 264 /* --------------------------------------------------------------------------------------------- */
 265 
 266 /**
 267  * Default command line callback
 268  *
 269  * @param w Widget object
 270  * @param msg message for handling
 271  * @param parm extra parameter such as key code
 272  *
 273  * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
 274  */
 275 
 276 static cb_ret_t
 277 command_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 278 {
 279     switch (msg)
 280     {
 281     case MSG_KEY:
 282         // Special case: we handle the enter key
 283         if (parm == '\n')
 284             return enter (INPUT (w));
 285         MC_FALLTHROUGH;
 286 
 287     default:
 288         return input_callback (w, sender, msg, parm, data);
 289     }
 290 }
 291 
 292 /* --------------------------------------------------------------------------------------------- */
 293 /*** public functions ****************************************************************************/
 294 /* --------------------------------------------------------------------------------------------- */
 295 
 296 WInput *
 297 command_new (int y, int x, int cols)
     /* [previous][next][first][last][top][bottom][index][help]  */
 298 {
 299     WInput *cmd;
 300     Widget *w;
 301 
 302     cmd = input_new (y, x, command_colors, cols, "", "cmdline",
 303                      INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
 304                          | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS
 305                          | INPUT_COMPLETE_SHELL_ESC);
 306     w = WIDGET (cmd);
 307     // Don't set WOP_SELECTABLE up, otherwise panels will be unselected
 308     widget_set_options (w, WOP_SELECTABLE, FALSE);
 309     // Add our hooks
 310     w->callback = command_callback;
 311 
 312     return cmd;
 313 }
 314 
 315 /* --------------------------------------------------------------------------------------------- */
 316 /**
 317  * Insert quoted text in input line.  The function is meant for the
 318  * command line, so the percent sign is quoted as well.
 319  *
 320  * @param in WInput object
 321  * @param text string for insertion
 322  * @param insert_extra_space add extra space
 323  */
 324 
 325 void
 326 command_insert (WInput *in, const char *text, gboolean insert_extra_space)
     /* [previous][next][first][last][top][bottom][index][help]  */
 327 {
 328     GString *quoted_text;
 329 
 330     quoted_text = name_quote (text, TRUE);
 331     if (quoted_text != NULL)
 332     {
 333         input_insert (in, quoted_text->str, insert_extra_space);
 334         g_string_free (quoted_text, TRUE);
 335     }
 336 }
 337 
 338 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */