root/src/filemanager/ext.c

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

DEFINITIONS

This source file includes following definitions.
  1. exec_cleanup_script
  2. exec_cleanup_file_name
  3. exec_get_file_name
  4. exec_expand_format
  5. exec_get_export_variables
  6. exec_make_shell_string
  7. exec_extension_view
  8. exec_extension_cd
  9. exec_extension
  10. get_popen_information
  11. get_file_type_local
  12. get_file_encoding_local
  13. regex_check_type
  14. check_old_extension_file
  15. load_extension_file
  16. flush_extension_file
  17. regex_command_for

   1 /*
   2    Extension dependent execution.
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Jakub Jelinek, 1995
   9    Miguel de Icaza, 1994
  10    Slava Zanko <slavazanko@gmail.com>, 2013
  11 
  12    This file is part of the Midnight Commander.
  13 
  14    The Midnight Commander is free software: you can redistribute it
  15    and/or modify it under the terms of the GNU General Public License as
  16    published by the Free Software Foundation, either version 3 of the License,
  17    or (at your option) any later version.
  18 
  19    The Midnight Commander is distributed in the hope that it will be useful,
  20    but WITHOUT ANY WARRANTY; without even the implied warranty of
  21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22    GNU General Public License for more details.
  23 
  24    You should have received a copy of the GNU General Public License
  25    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  26  */
  27 
  28 /** \file  ext.c
  29  *  \brief Source: extension dependent execution
  30  */
  31 
  32 #include <config.h>
  33 
  34 #include <ctype.h>
  35 #include <errno.h>
  36 #include <stdlib.h>
  37 #include <stdio.h>
  38 #include <string.h>
  39 #include <unistd.h>
  40 
  41 #include "lib/global.h"
  42 #include "lib/tty/tty.h"
  43 #include "lib/search.h"
  44 #include "lib/fileloc.h"
  45 #include "lib/mcconfig.h"
  46 #include "lib/util.h"
  47 #include "lib/vfs/vfs.h"
  48 #include "lib/widget.h"
  49 #ifdef HAVE_CHARSET
  50 #    include "lib/charsets.h"  // get_codepage_index
  51 #endif
  52 
  53 #ifdef USE_FILE_CMD
  54 #    include "src/setup.h"  // use_file_to_check_type
  55 #endif
  56 #include "src/execute.h"
  57 #include "src/history.h"
  58 #include "src/usermenu.h"
  59 
  60 #include "src/consaver/cons.saver.h"
  61 #include "src/viewer/mcviewer.h"
  62 
  63 #ifdef HAVE_CHARSET
  64 #    include "src/selcodepage.h"  // do_set_codepage
  65 #endif
  66 
  67 #include "filemanager.h"  // current_panel
  68 #include "panel.h"        // panel_cd
  69 
  70 #include "ext.h"
  71 
  72 /*** global variables ****************************************************************************/
  73 
  74 /*** file scope macro definitions ****************************************************************/
  75 
  76 #ifdef USE_FILE_CMD
  77 #    ifdef FILE_B
  78 #        define FILE_CMD "file -z " FILE_B FILE_S FILE_L
  79 #    else
  80 #        define FILE_CMD "file -z " FILE_S FILE_L
  81 #    endif
  82 #endif
  83 
  84 /*** file scope type declarations ****************************************************************/
  85 
  86 typedef char *(*quote_func_t) (const char *name, gboolean quote_percent);
  87 
  88 /*** forward declarations (file scope functions) *************************************************/
  89 
  90 /*** file scope variables ************************************************************************/
  91 
  92 /* This variable points to a copy of the mc.ext file in memory
  93  * With this we avoid loading/parsing the file each time we
  94  * need it
  95  */
  96 static mc_config_t *ext_ini = NULL;
  97 static gchar **ext_ini_groups = NULL;
  98 static vfs_path_t *localfilecopy_vpath = NULL;
  99 static char buffer[BUF_1K];
 100 
 101 static char *pbuffer = NULL;
 102 static time_t localmtime = 0;
 103 static quote_func_t quote_func = name_quote;
 104 static gboolean run_view = FALSE;
 105 static gboolean is_cd = FALSE;
 106 static gboolean written_nonspace = FALSE;
 107 static gboolean do_local_copy = FALSE;
 108 
 109 static const char *descr_group = "mc.ext.ini";
 110 static const char *default_group = "Default";
 111 
 112 /* --------------------------------------------------------------------------------------------- */
 113 /*** file scope functions ************************************************************************/
 114 /* --------------------------------------------------------------------------------------------- */
 115 
 116 static void
 117 exec_cleanup_script (vfs_path_t *script_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 118 {
 119     if (script_vpath != NULL)
 120     {
 121         (void) mc_unlink (script_vpath);
 122         vfs_path_free (script_vpath, TRUE);
 123     }
 124 }
 125 
 126 /* --------------------------------------------------------------------------------------------- */
 127 
 128 static void
 129 exec_cleanup_file_name (const vfs_path_t *filename_vpath, gboolean has_changed)
     /* [previous][next][first][last][top][bottom][index][help]  */
 130 {
 131     if (localfilecopy_vpath == NULL)
 132         return;
 133 
 134     if (has_changed)
 135     {
 136         struct stat mystat;
 137 
 138         mc_stat (localfilecopy_vpath, &mystat);
 139         has_changed = localmtime != mystat.st_mtime;
 140     }
 141     mc_ungetlocalcopy (filename_vpath, localfilecopy_vpath, has_changed);
 142     vfs_path_free (localfilecopy_vpath, TRUE);
 143     localfilecopy_vpath = NULL;
 144 }
 145 
 146 /* --------------------------------------------------------------------------------------------- */
 147 
 148 static char *
 149 exec_get_file_name (const vfs_path_t *filename_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 150 {
 151     if (!do_local_copy)
 152         return quote_func (vfs_path_get_last_path_str (filename_vpath), FALSE);
 153 
 154     if (localfilecopy_vpath == NULL)
 155     {
 156         struct stat mystat;
 157         localfilecopy_vpath = mc_getlocalcopy (filename_vpath);
 158         if (localfilecopy_vpath == NULL)
 159             return NULL;
 160 
 161         mc_stat (localfilecopy_vpath, &mystat);
 162         localmtime = mystat.st_mtime;
 163     }
 164 
 165     return quote_func (vfs_path_get_last_path_str (localfilecopy_vpath), FALSE);
 166 }
 167 
 168 /* --------------------------------------------------------------------------------------------- */
 169 
 170 static char *
 171 exec_expand_format (char symbol, gboolean is_result_quoted)
     /* [previous][next][first][last][top][bottom][index][help]  */
 172 {
 173     char *text;
 174 
 175     text = expand_format (NULL, symbol, TRUE);
 176     if (is_result_quoted && text != NULL)
 177     {
 178         char *quoted_text;
 179 
 180         quoted_text = g_strdup_printf ("\"%s\"", text);
 181         g_free (text);
 182         text = quoted_text;
 183     }
 184     return text;
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 
 189 static GString *
 190 exec_get_export_variables (const vfs_path_t *filename_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 191 {
 192     char *text;
 193     GString *export_vars_string;
 194     size_t i;
 195 
 196     struct
 197     {
 198         const char symbol;
 199         const char *name;
 200         const gboolean is_result_quoted;
 201     } export_variables[] = {
 202         { 'p', "MC_EXT_BASENAME", FALSE },
 203         { 'd', "MC_EXT_CURRENTDIR", FALSE },
 204         { 's', "MC_EXT_SELECTED", TRUE },
 205         { 't', "MC_EXT_ONLYTAGGED", TRUE },
 206         { '\0', NULL, FALSE },
 207     };
 208 
 209     text = exec_get_file_name (filename_vpath);
 210     if (text == NULL)
 211         return NULL;
 212 
 213     export_vars_string = g_string_new ("MC_EXT_FILENAME=");
 214     g_string_append_printf (export_vars_string, "%s\nexport MC_EXT_FILENAME\n", text);
 215     g_free (text);
 216 
 217     for (i = 0; export_variables[i].name != NULL; i++)
 218     {
 219         text =
 220             exec_expand_format (export_variables[i].symbol, export_variables[i].is_result_quoted);
 221         if (text != NULL)
 222         {
 223             g_string_append_printf (export_vars_string, "%s=%s\nexport %s\n",
 224                                     export_variables[i].name, text, export_variables[i].name);
 225             g_free (text);
 226         }
 227     }
 228 
 229     return export_vars_string;
 230 }
 231 
 232 /* --------------------------------------------------------------------------------------------- */
 233 
 234 static GString *
 235 exec_make_shell_string (const char *lc_data, const vfs_path_t *filename_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 236 {
 237     GString *shell_string;
 238     char lc_prompt[80] = "\0";
 239     gboolean parameter_found = FALSE;
 240     gboolean expand_prefix_found = FALSE;
 241 
 242     shell_string = g_string_new ("");
 243 
 244     for (; *lc_data != '\0' && *lc_data != '\n'; lc_data++)
 245     {
 246         if (parameter_found)
 247         {
 248             if (*lc_data == '}')
 249             {
 250                 char *parameter;
 251 
 252                 parameter_found = FALSE;
 253                 parameter = input_dialog (_ ("Parameter"), lc_prompt, MC_HISTORY_EXT_PARAMETER, "",
 254                                           INPUT_COMPLETE_NONE);
 255                 if (parameter == NULL)
 256                 {
 257                     // User canceled
 258                     g_string_free (shell_string, TRUE);
 259                     exec_cleanup_file_name (filename_vpath, FALSE);
 260                     return NULL;
 261                 }
 262                 g_string_append (shell_string, parameter);
 263                 written_nonspace = TRUE;
 264                 g_free (parameter);
 265             }
 266             else
 267             {
 268                 size_t len = strlen (lc_prompt);
 269 
 270                 if (len < sizeof (lc_prompt) - 1)
 271                 {
 272                     lc_prompt[len] = *lc_data;
 273                     lc_prompt[len + 1] = '\0';
 274                 }
 275             }
 276         }
 277         else if (expand_prefix_found)
 278         {
 279             expand_prefix_found = FALSE;
 280             if (*lc_data == '{')
 281                 parameter_found = TRUE;
 282             else
 283             {
 284                 int i;
 285 
 286                 i = check_format_view (lc_data);
 287                 if (i != 0)
 288                 {
 289                     lc_data += i - 1;
 290                     run_view = TRUE;
 291                 }
 292                 else
 293                 {
 294                     i = check_format_cd (lc_data);
 295                     if (i > 0)
 296                     {
 297                         is_cd = TRUE;
 298                         quote_func = fake_name_quote;
 299                         do_local_copy = FALSE;
 300                         pbuffer = buffer;
 301                         lc_data += i - 1;
 302                     }
 303                     else
 304                     {
 305                         char *v;
 306 
 307                         i = check_format_var (lc_data, &v);
 308                         if (i > 0)
 309                         {
 310                             g_string_append (shell_string, v);
 311                             g_free (v);
 312                             lc_data += i;
 313                         }
 314                         else
 315                         {
 316                             char *text;
 317 
 318                             if (*lc_data != 'f')
 319                                 text = expand_format (NULL, *lc_data, !is_cd);
 320                             else
 321                             {
 322                                 text = exec_get_file_name (filename_vpath);
 323                                 if (text == NULL)
 324                                 {
 325                                     g_string_free (shell_string, TRUE);
 326                                     return NULL;
 327                                 }
 328                             }
 329 
 330                             if (text != NULL)
 331                             {
 332                                 if (!is_cd)
 333                                     g_string_append (shell_string, text);
 334                                 else
 335                                 {
 336                                     strcpy (pbuffer, text);
 337                                     pbuffer = strchr (pbuffer, '\0');
 338                                 }
 339 
 340                                 g_free (text);
 341                             }
 342 
 343                             written_nonspace = TRUE;
 344                         }
 345                     }
 346                 }
 347             }
 348         }
 349         else if (*lc_data == '%')
 350             expand_prefix_found = TRUE;
 351         else
 352         {
 353             if (!whitespace (*lc_data))
 354                 written_nonspace = TRUE;
 355             if (is_cd)
 356                 *(pbuffer++) = *lc_data;
 357             else
 358                 g_string_append_c (shell_string, *lc_data);
 359         }
 360     }  // for
 361 
 362     return shell_string;
 363 }
 364 
 365 /* --------------------------------------------------------------------------------------------- */
 366 
 367 static void
 368 exec_extension_view (void *target, char *cmd, const vfs_path_t *filename_vpath, int start_line)
     /* [previous][next][first][last][top][bottom][index][help]  */
 369 {
 370     mcview_mode_flags_t def_flags = {
 371         .wrap = FALSE,
 372         .hex = mcview_global_flags.hex,
 373         .magic = FALSE,
 374         .nroff = mcview_global_flags.nroff,
 375     };
 376 
 377     mcview_mode_flags_t changed_flags;
 378 
 379     mcview_clear_mode_flags (&changed_flags);
 380     mcview_altered_flags.hex = FALSE;
 381     mcview_altered_flags.nroff = FALSE;
 382     if (def_flags.hex != mcview_global_flags.hex)
 383         changed_flags.hex = TRUE;
 384     if (def_flags.nroff != mcview_global_flags.nroff)
 385         changed_flags.nroff = TRUE;
 386 
 387     if (target == NULL)
 388         mcview_viewer (cmd, filename_vpath, start_line, 0, 0);
 389     else
 390         mcview_load ((WView *) target, cmd, vfs_path_as_str (filename_vpath), start_line, 0, 0);
 391 
 392     if (changed_flags.hex && !mcview_altered_flags.hex)
 393         mcview_global_flags.hex = def_flags.hex;
 394     if (changed_flags.nroff && !mcview_altered_flags.nroff)
 395         mcview_global_flags.nroff = def_flags.nroff;
 396 
 397     dialog_switch_process_pending ();
 398 }
 399 
 400 /* --------------------------------------------------------------------------------------------- */
 401 
 402 static void
 403 exec_extension_cd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 404 {
 405     char *q;
 406     vfs_path_t *p_vpath;
 407 
 408     *pbuffer = '\0';
 409     pbuffer = buffer;
 410     /* Search last non-space character. Start search at the end in order
 411        not to short filenames containing spaces. */
 412     q = pbuffer + strlen (pbuffer) - 1;
 413     while (q >= pbuffer && whitespace (*q))
 414         q--;
 415     q[1] = 0;
 416 
 417     p_vpath = vfs_path_from_str_flags (pbuffer, VPF_NO_CANON);
 418     panel_cd (panel, p_vpath, cd_parse_command);
 419     vfs_path_free (p_vpath, TRUE);
 420 }
 421 
 422 /* --------------------------------------------------------------------------------------------- */
 423 
 424 static vfs_path_t *
 425 exec_extension (WPanel *panel, void *target, const vfs_path_t *filename_vpath, const char *lc_data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 426                 int start_line)
 427 {
 428     GString *shell_string, *export_variables;
 429     vfs_path_t *script_vpath = NULL;
 430     int cmd_file_fd;
 431     FILE *cmd_file;
 432     char *cmd = NULL;
 433 
 434     pbuffer = NULL;
 435     localmtime = 0;
 436     quote_func = name_quote;
 437     run_view = FALSE;
 438     is_cd = FALSE;
 439     written_nonspace = FALSE;
 440 
 441     // Avoid making a local copy if we are doing a cd
 442     do_local_copy = !vfs_file_is_local (filename_vpath);
 443 
 444     shell_string = exec_make_shell_string (lc_data, filename_vpath);
 445     if (shell_string == NULL)
 446         goto ret;
 447 
 448     if (is_cd)
 449     {
 450         exec_extension_cd (panel);
 451         g_string_free (shell_string, TRUE);
 452         goto ret;
 453     }
 454 
 455     /*
 456      * All commands should be run in /bin/sh regardless of user shell.
 457      * To do that, create temporary shell script and run it.
 458      * Sometimes it's not needed (e.g. for %cd and %view commands),
 459      * but it's easier to create it anyway.
 460      */
 461     cmd_file_fd = mc_mkstemps (&script_vpath, "mcext", SCRIPT_SUFFIX);
 462 
 463     if (cmd_file_fd == -1)
 464     {
 465         message (D_ERROR, MSG_ERROR, _ ("Cannot create temporary command file\n%s"),
 466                  unix_error_string (errno));
 467         g_string_free (shell_string, TRUE);
 468         goto ret;
 469     }
 470 
 471     cmd_file = fdopen (cmd_file_fd, "w");
 472     fputs ("#! /bin/sh\n\n", cmd_file);
 473 
 474     export_variables = exec_get_export_variables (filename_vpath);
 475     if (export_variables != NULL)
 476     {
 477         fputs (export_variables->str, cmd_file);
 478         g_string_free (export_variables, TRUE);
 479     }
 480 
 481     fputs (shell_string->str, cmd_file);
 482     g_string_free (shell_string, TRUE);
 483 
 484     /*
 485      * Make the script remove itself when it finishes.
 486      * Don't do it for the viewer - it may need to rerun the script,
 487      * so we clean up after calling view().
 488      */
 489     if (!run_view)
 490         fprintf (cmd_file, "\n/bin/rm -f %s\n", vfs_path_as_str (script_vpath));
 491 
 492     fclose (cmd_file);
 493 
 494     if ((run_view && !written_nonspace) || is_cd)
 495     {
 496         exec_cleanup_script (script_vpath);
 497         script_vpath = NULL;
 498     }
 499     else
 500     {
 501         // Set executable flag on the command file ...
 502         mc_chmod (script_vpath, S_IRWXU);
 503         // ... but don't rely on it - run /bin/sh explicitly
 504         cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (script_vpath), (char *) NULL);
 505     }
 506 
 507     if (run_view)
 508     {
 509         // If we've written whitespace only, then just load filename into view
 510         if (!written_nonspace)
 511             exec_extension_view (target, NULL, filename_vpath, start_line);
 512         else
 513             exec_extension_view (target, cmd, filename_vpath, start_line);
 514     }
 515     else
 516     {
 517         shell_execute (cmd, EXECUTE_INTERNAL);
 518         if (mc_global.tty.console_flag != '\0')
 519         {
 520             handle_console (CONSOLE_SAVE);
 521             if (output_lines != 0 && mc_global.keybar_visible)
 522             {
 523                 unsigned char end_line;
 524 
 525                 end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
 526                 show_console_contents (output_start_y, end_line - output_lines, end_line);
 527             }
 528         }
 529     }
 530 
 531     g_free (cmd);
 532 
 533     exec_cleanup_file_name (filename_vpath, TRUE);
 534 ret:
 535     return script_vpath;
 536 }
 537 
 538 /* --------------------------------------------------------------------------------------------- */
 539 /**
 540  * Run cmd_file with args, put result into buf.
 541  * If error, put '\0' into buf[0]
 542  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 543  *
 544  * NOTES: buf is null-terminated string.
 545  */
 546 
 547 #ifdef USE_FILE_CMD
 548 static int
 549 get_popen_information (const char *cmd_file, const char *args, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 550 {
 551     gboolean read_bytes = FALSE;
 552     char *command;
 553     FILE *f;
 554 
 555     command = g_strconcat (cmd_file, args, " 2>/dev/null", (char *) NULL);
 556     f = popen (command, "r");
 557     g_free (command);
 558 
 559     if (f != NULL)
 560     {
 561 #    ifdef __QNXNTO__
 562         if (setvbuf (f, NULL, _IOFBF, 0) != 0)
 563         {
 564             (void) pclose (f);
 565             return -1;
 566         }
 567 #    endif
 568         read_bytes = (fgets (buf, buflen, f) != NULL);
 569         if (!read_bytes)
 570             buf[0] = '\0';  // Paranoid termination
 571         pclose (f);
 572     }
 573     else
 574     {
 575         buf[0] = '\0';  // Paranoid termination
 576         return -1;
 577     }
 578 
 579     buf[buflen - 1] = '\0';
 580 
 581     return read_bytes ? 1 : 0;
 582 }
 583 
 584 /* --------------------------------------------------------------------------------------------- */
 585 /**
 586  * Run the "file" command on the local file.
 587  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 588  */
 589 
 590 static int
 591 get_file_type_local (const vfs_path_t *filename_vpath, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 592 {
 593     char *filename_quoted;
 594     int ret = 0;
 595 
 596     filename_quoted = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
 597     if (filename_quoted != NULL)
 598     {
 599         ret = get_popen_information (FILE_CMD, filename_quoted, buf, buflen);
 600         g_free (filename_quoted);
 601     }
 602 
 603     return ret;
 604 }
 605 
 606 /* --------------------------------------------------------------------------------------------- */
 607 /**
 608  * Run the "enca" command on the local file.
 609  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 610  */
 611 
 612 #    ifdef HAVE_CHARSET
 613 static int
 614 get_file_encoding_local (const vfs_path_t *filename_vpath, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 615 {
 616     char *filename_quoted;
 617     int ret = 0;
 618 
 619     filename_quoted = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
 620     if (filename_quoted != NULL)
 621     {
 622         char *lang;
 623 
 624         lang = name_quote (autodetect_codeset, FALSE);
 625         if (lang != NULL)
 626         {
 627             char *args;
 628 
 629             args = g_strdup_printf (" -L %s -i %s", lang, filename_quoted);
 630             g_free (lang);
 631 
 632             ret = get_popen_information ("enca", args, buf, buflen);
 633             g_free (args);
 634         }
 635 
 636         g_free (filename_quoted);
 637     }
 638 
 639     return ret;
 640 }
 641 #    endif
 642 
 643 /* --------------------------------------------------------------------------------------------- */
 644 /**
 645  * Invoke the "file" command on the file and match its output against PTR.
 646  * have_type is a flag that is set if we already have tried to determine
 647  * the type of that file.
 648  * Return TRUE for match, FALSE otherwise.
 649  */
 650 
 651 static gboolean
 652 regex_check_type (const vfs_path_t *filename_vpath, const char *ptr, gboolean case_insense,
     /* [previous][next][first][last][top][bottom][index][help]  */
 653                   gboolean *have_type, GError **mcerror)
 654 {
 655     gboolean found = FALSE;
 656 
 657     // Following variables are valid if *have_type is TRUE
 658     static char content_string[2048];
 659     static size_t content_shift = 0;
 660     static int got_data = 0;
 661 
 662     mc_return_val_if_error (mcerror, FALSE);
 663 
 664     if (!*have_type)
 665     {
 666         vfs_path_t *localfile_vpath;
 667 
 668 #    ifdef HAVE_CHARSET
 669         static char encoding_id[21];  // CSISO51INISCYRILLIC -- 20
 670         int got_encoding_data;
 671 #    endif
 672 
 673         // Don't repeate even unsuccessful checks
 674         *have_type = TRUE;
 675 
 676         localfile_vpath = mc_getlocalcopy (filename_vpath);
 677         if (localfile_vpath == NULL)
 678         {
 679             mc_propagate_error (mcerror, 0, _ ("Cannot fetch a local copy of %s"),
 680                                 vfs_path_as_str (filename_vpath));
 681             return FALSE;
 682         }
 683 
 684 #    ifdef HAVE_CHARSET
 685         got_encoding_data = is_autodetect_codeset_enabled
 686             ? get_file_encoding_local (localfile_vpath, encoding_id, sizeof (encoding_id))
 687             : 0;
 688 
 689         if (got_encoding_data > 0)
 690         {
 691             char *pp;
 692             int cp_id;
 693 
 694             pp = strchr (encoding_id, '\n');
 695             if (pp != NULL)
 696                 *pp = '\0';
 697 
 698             cp_id = get_codepage_index (encoding_id);
 699             if (cp_id == -1)
 700                 cp_id = default_source_codepage;
 701 
 702             do_set_codepage (cp_id);
 703         }
 704 #    endif
 705 
 706         got_data = get_file_type_local (localfile_vpath, content_string, sizeof (content_string));
 707 
 708         mc_ungetlocalcopy (filename_vpath, localfile_vpath, FALSE);
 709 
 710         if (got_data > 0)
 711         {
 712             char *pp;
 713 
 714             pp = strchr (content_string, '\n');
 715             if (pp != NULL)
 716                 *pp = '\0';
 717 
 718 #    ifndef FILE_B
 719             {
 720                 const char *real_name;  // name used with "file"
 721                 size_t real_len;
 722 
 723                 real_name = vfs_path_get_last_path_str (localfile_vpath);
 724                 real_len = strlen (real_name);
 725 
 726                 if (strncmp (content_string, real_name, real_len) == 0)
 727                 {
 728                     // Skip "real_name: "
 729                     content_shift = real_len;
 730 
 731                     // Solaris' file prints tab(s) after ':'
 732                     if (content_string[content_shift] == ':')
 733                         for (content_shift++; whitespace (content_string[content_shift]);
 734                              content_shift++)
 735                             ;
 736                 }
 737             }
 738 #    endif
 739         }
 740         else
 741         {
 742             // No data
 743             content_string[0] = '\0';
 744         }
 745         vfs_path_free (localfile_vpath, TRUE);
 746     }
 747 
 748     if (got_data == -1)
 749     {
 750         mc_propagate_error (mcerror, 0, "%s", _ ("Pipe failed"));
 751         return FALSE;
 752     }
 753 
 754     if (content_string[0] != '\0')
 755     {
 756         mc_search_t *search;
 757 
 758         search = mc_search_new (ptr, DEFAULT_CHARSET);
 759         if (search != NULL)
 760         {
 761             search->search_type = MC_SEARCH_T_REGEX;
 762             search->is_case_sensitive = !case_insense;
 763             found = mc_search_run (search, content_string + content_shift, 0,
 764                                    sizeof (content_string) - 1, NULL);
 765             mc_search_free (search);
 766         }
 767         else
 768         {
 769             mc_propagate_error (mcerror, 0, "%s", _ ("Regular expression error"));
 770         }
 771     }
 772 
 773     return found;
 774 }
 775 #endif
 776 
 777 /* --------------------------------------------------------------------------------------------- */
 778 
 779 static void
 780 check_old_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 781 {
 782     char *extension_old_file;
 783 
 784     extension_old_file = mc_config_get_full_path (MC_EXT_OLD_FILE);
 785     if (exist_file (extension_old_file))
 786         message (D_ERROR, _ ("Warning"),
 787                  _ ("You have an outdated %s file.\nMidnight Commander now uses %s file.\n"
 788                     "Please copy your modifications of the old file to the new one."),
 789                  extension_old_file, MC_EXT_FILE);
 790     g_free (extension_old_file);
 791 }
 792 
 793 /* --------------------------------------------------------------------------------------------- */
 794 
 795 static gboolean
 796 load_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 797 {
 798     char *extension_file;
 799     gboolean mc_user_ext = TRUE;
 800     gboolean home_error = FALSE;
 801 
 802     extension_file = mc_config_get_full_path (MC_EXT_FILE);
 803     if (!exist_file (extension_file))
 804     {
 805         g_free (extension_file);
 806 
 807         check_old_extension_file ();
 808 
 809     check_stock_mc_ext:
 810         extension_file = mc_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
 811         if (!exist_file (extension_file))
 812         {
 813             g_free (extension_file);
 814             extension_file =
 815                 mc_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
 816             if (!exist_file (extension_file))
 817                 MC_PTR_FREE (extension_file);
 818         }
 819         mc_user_ext = FALSE;
 820     }
 821 
 822     if (extension_file != NULL)
 823     {
 824         ext_ini = mc_config_init (extension_file, TRUE);
 825         g_free (extension_file);
 826     }
 827     if (ext_ini == NULL)
 828         return FALSE;
 829 
 830     // Check version
 831     if (!mc_config_has_group (ext_ini, descr_group))
 832     {
 833         flush_extension_file ();
 834 
 835         if (!mc_user_ext)
 836         {
 837             message (D_ERROR, MSG_ERROR,
 838                      _ ("The format of the\n%s%s\nfile has changed with version 4.0.\n"
 839                         "It seems that the installation has failed.\nPlease fetch a fresh copy "
 840                         "from the Midnight Commander package."),
 841                      mc_global.sysconfig_dir, MC_EXT_FILE);
 842             return FALSE;
 843         }
 844 
 845         home_error = TRUE;
 846         goto check_stock_mc_ext;
 847     }
 848 
 849     if (home_error)
 850     {
 851         extension_file = mc_config_get_full_path (MC_EXT_FILE);
 852         message (
 853             D_ERROR, MSG_ERROR,
 854             _ ("The format of the\n%s\nfile has changed with version 4.0.\nYou may either want "
 855                "to copy it from\n%s%s\nor use that file as an example of how to write it."),
 856             extension_file, mc_global.sysconfig_dir, MC_EXT_FILE);
 857         g_free (extension_file);
 858     }
 859 
 860     return TRUE;
 861 }
 862 
 863 /* --------------------------------------------------------------------------------------------- */
 864 /*** public functions ****************************************************************************/
 865 /* --------------------------------------------------------------------------------------------- */
 866 
 867 void
 868 flush_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 869 {
 870     g_strfreev (ext_ini_groups);
 871     ext_ini_groups = NULL;
 872 
 873     mc_config_deinit (ext_ini);
 874     ext_ini = NULL;
 875 }
 876 
 877 /* --------------------------------------------------------------------------------------------- */
 878 /**
 879  * The second argument is action, i.e. Open, View or Edit
 880  * Use target object to open file in.
 881  *
 882  * This function returns:
 883  *
 884  * -1 for a failure or user interrupt
 885  * 0 if no command was run
 886  * 1 if some command was run
 887  *
 888  * If action == "View" then a parameter is checked in the form of "View:%d",
 889  * if the value for %d exists, then the viewer is started up at that line number.
 890  */
 891 
 892 int
 893 regex_command_for (void *target, const vfs_path_t *filename_vpath, const char *action,
     /* [previous][next][first][last][top][bottom][index][help]  */
 894                    vfs_path_t **script_vpath)
 895 {
 896     const char *filename;
 897     size_t filename_len;
 898     gboolean found = FALSE;
 899     gboolean error_flag = FALSE;
 900     int ret = 0;
 901     struct stat mystat;
 902     int view_at_line_number = 0;
 903 #ifdef USE_FILE_CMD
 904     gboolean have_type = FALSE;  // Flag used by regex_check_type()
 905 #endif
 906     char **group_iter;
 907     char *include_group = NULL;
 908     const char *current_group;
 909 
 910     if (filename_vpath == NULL)
 911         return 0;
 912 
 913     if (script_vpath != NULL)
 914         *script_vpath = NULL;
 915 
 916     // Check for the special View:%d parameter
 917     if (strncmp (action, "View:", 5) == 0)
 918     {
 919         view_at_line_number = atoi (action + 5);
 920         action = "View";
 921     }
 922 
 923     if (ext_ini == NULL && !load_extension_file ())
 924         return 0;
 925 
 926     mc_stat (filename_vpath, &mystat);
 927 
 928     filename = vfs_path_get_last_path_str (filename_vpath);
 929     filename = x_basename (filename);
 930     filename_len = strlen (filename);
 931 
 932     if (ext_ini_groups == NULL)
 933         ext_ini_groups = mc_config_get_groups (ext_ini, NULL);
 934 
 935     // find matched type, regex or shell pattern
 936     for (group_iter = ext_ini_groups; *group_iter != NULL && !found; group_iter++)
 937     {
 938         enum
 939         {
 940             TYPE_UNUSED,
 941             TYPE_NOT_FOUND,
 942             TYPE_FOUND
 943         } type_state = TYPE_UNUSED;
 944 
 945         const gchar *g = *group_iter;
 946         gchar *pattern;
 947         gboolean ignore_case;
 948 
 949         if (strcmp (g, descr_group) == 0 || strncmp (g, "Include/", 8) == 0
 950             || strcmp (g, default_group) == 0)
 951             continue;
 952 
 953         /* The "Directory" parameter is a special case: if it's present then
 954            "Type", "Regex", and "Shell" parameters are ignored */
 955         pattern = mc_config_get_string_raw (ext_ini, g, "Directory", NULL);
 956         if (pattern != NULL)
 957         {
 958             found = S_ISDIR (mystat.st_mode)
 959                 && mc_search (pattern, DEFAULT_CHARSET, vfs_path_as_str (filename_vpath),
 960                               MC_SEARCH_T_REGEX);
 961             g_free (pattern);
 962 
 963             continue;  // stop if found
 964         }
 965 
 966 #ifdef USE_FILE_CMD
 967         if (use_file_to_check_type)
 968         {
 969             pattern = mc_config_get_string_raw (ext_ini, g, "Type", NULL);
 970             if (pattern != NULL)
 971             {
 972                 GError *mcerror = NULL;
 973 
 974                 ignore_case = mc_config_get_bool (ext_ini, g, "TypeIgnoreCase", FALSE);
 975                 type_state =
 976                     regex_check_type (filename_vpath, pattern, ignore_case, &have_type, &mcerror)
 977                     ? TYPE_FOUND
 978                     : TYPE_NOT_FOUND;
 979                 g_free (pattern);
 980 
 981                 if (mc_error_message (&mcerror, NULL))
 982                     error_flag = TRUE;  // leave it if file cannot be opened
 983 
 984                 if (type_state == TYPE_NOT_FOUND)
 985                     continue;
 986             }
 987         }
 988 #endif
 989 
 990         pattern = mc_config_get_string_raw (ext_ini, g, "Regex", NULL);
 991         if (pattern != NULL)
 992         {
 993             mc_search_t *search;
 994 
 995             ignore_case = mc_config_get_bool (ext_ini, g, "RegexIgnoreCase", FALSE);
 996             search = mc_search_new (pattern, DEFAULT_CHARSET);
 997             g_free (pattern);
 998 
 999             if (search != NULL)
1000             {
1001                 search->search_type = MC_SEARCH_T_REGEX;
1002                 search->is_case_sensitive = !ignore_case;
1003                 found = mc_search_run (search, filename, 0, filename_len, NULL);
1004                 mc_search_free (search);
1005             }
1006 
1007             found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
1008         }
1009         else
1010         {
1011             pattern = mc_config_get_string_raw (ext_ini, g, "Shell", NULL);
1012             if (pattern != NULL)
1013             {
1014                 int (*cmp_func) (const char *s1, const char *s2, size_t n);
1015                 size_t pattern_len;
1016 
1017                 ignore_case = mc_config_get_bool (ext_ini, g, "ShellIgnoreCase", FALSE);
1018                 cmp_func = ignore_case ? strncasecmp : strncmp;
1019                 pattern_len = strlen (pattern);
1020 
1021                 if (*pattern == '.' && filename_len >= pattern_len)
1022                     found =
1023                         cmp_func (pattern, filename + filename_len - pattern_len, pattern_len) == 0;
1024                 else
1025                     found = pattern_len == filename_len
1026                         && cmp_func (pattern, filename, filename_len) == 0;
1027 
1028                 g_free (pattern);
1029 
1030                 found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
1031             }
1032             else
1033                 found = type_state == TYPE_FOUND;
1034         }
1035     }
1036 
1037     // group is found, process actions
1038     if (found)
1039     {
1040         char *include_value;
1041 
1042         group_iter--;
1043 
1044         // "Include" parameter has the highest priority over any actions
1045         include_value = mc_config_get_string_raw (ext_ini, *group_iter, "Include", NULL);
1046         if (include_value != NULL)
1047         {
1048             // find "Include/include_value" group
1049             include_group = g_strconcat ("Include/", include_value, (char *) NULL);
1050             g_free (include_value);
1051             found = mc_config_has_group (ext_ini, include_group);
1052         }
1053     }
1054 
1055     if (found)
1056         current_group = include_group != NULL ? include_group : *group_iter;
1057     else
1058     {
1059         current_group = default_group;
1060         found = mc_config_has_group (ext_ini, current_group);
1061     }
1062 
1063     if (found && !error_flag)
1064     {
1065         gchar *action_value;
1066 
1067         action_value = mc_config_get_string_raw (ext_ini, current_group, action, NULL);
1068         if (action_value == NULL)
1069         {
1070             // Not found, try the action from default section
1071             action_value = mc_config_get_string_raw (ext_ini, default_group, action, NULL);
1072             found = (action_value != NULL && *action_value != '\0');
1073         }
1074         else
1075         {
1076             // If action's value is empty, ignore action from default section
1077             found = (*action_value != '\0');
1078         }
1079 
1080         if (found)
1081         {
1082             vfs_path_t *sv;
1083 
1084             sv = exec_extension (current_panel, target, filename_vpath, action_value,
1085                                  view_at_line_number);
1086             if (script_vpath != NULL)
1087                 *script_vpath = sv;
1088             else
1089                 exec_cleanup_script (sv);
1090 
1091             ret = 1;
1092         }
1093 
1094         g_free (action_value);
1095     }
1096 
1097     g_free (include_group);
1098 
1099     return (error_flag ? -1 : ret);
1100 }
1101 
1102 /* --------------------------------------------------------------------------------------------- */

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