Manual pages: mcmcdiffmceditmcview

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

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