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 GString *exec_make_shell_string (const char *lc_data, const vfs_path_t *filename_vpath,
  85                                              GString **buffer);
  86 
  87 MC_TESTABLE GString *exec_get_export_variables (const vfs_path_t *filename_vpath);
  88 #endif
  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 
 100 static time_t localmtime = 0;
 101 static quote_fn quote_func = name_quote;
 102 static gboolean run_view = FALSE;
 103 static gboolean is_cd = FALSE;
 104 static gboolean written_nonspace = FALSE;
 105 static gboolean do_local_copy = FALSE;
 106 
 107 static const char *descr_group = "mc.ext.ini";
 108 static const char *default_group = "Default";
 109 
 110 /* --------------------------------------------------------------------------------------------- */
 111 /*** file scope functions ************************************************************************/
 112 /* --------------------------------------------------------------------------------------------- */
 113 
 114 static void
 115 exec_cleanup_script (vfs_path_t *script_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 116 {
 117     if (script_vpath != NULL)
 118     {
 119         (void) mc_unlink (script_vpath);
 120         vfs_path_free (script_vpath, TRUE);
 121     }
 122 }
 123 
 124 /* --------------------------------------------------------------------------------------------- */
 125 
 126 static void
 127 exec_cleanup_file_name (const vfs_path_t *filename_vpath, gboolean has_changed)
     /* [previous][next][first][last][top][bottom][index][help]  */
 128 {
 129     if (localfilecopy_vpath == NULL)
 130         return;
 131 
 132     if (has_changed)
 133     {
 134         struct stat mystat;
 135 
 136         mc_stat (localfilecopy_vpath, &mystat);
 137         has_changed = localmtime != mystat.st_mtime;
 138     }
 139     mc_ungetlocalcopy (filename_vpath, localfilecopy_vpath, has_changed);
 140     vfs_path_free (localfilecopy_vpath, TRUE);
 141     localfilecopy_vpath = NULL;
 142 }
 143 
 144 /* --------------------------------------------------------------------------------------------- */
 145 
 146 static GString *
 147 exec_get_file_name (const vfs_path_t *filename_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 148 {
 149     if (!do_local_copy)
 150         return quote_func (vfs_path_get_last_path_str (filename_vpath), FALSE);
 151 
 152     if (localfilecopy_vpath == NULL)
 153     {
 154         struct stat mystat;
 155         localfilecopy_vpath = mc_getlocalcopy (filename_vpath);
 156         if (localfilecopy_vpath == NULL)
 157             return NULL;
 158 
 159         mc_stat (localfilecopy_vpath, &mystat);
 160         localmtime = mystat.st_mtime;
 161     }
 162 
 163     return quote_func (vfs_path_get_last_path_str (localfilecopy_vpath), FALSE);
 164 }
 165 
 166 /* --------------------------------------------------------------------------------------------- */
 167 
 168 static GString *
 169 exec_expand_format (char symbol, gboolean is_result_quoted)
     /* [previous][next][first][last][top][bottom][index][help]  */
 170 {
 171     GString *text;
 172 
 173     text = expand_format (NULL, symbol, TRUE);
 174     if (is_result_quoted && text != NULL)
 175     {
 176         g_string_prepend_c (text, '"');
 177         g_string_append_c (text, '"');
 178     }
 179     return text;
 180 }
 181 
 182 /* --------------------------------------------------------------------------------------------- */
 183 
 184 MC_TESTABLE GString *
 185 exec_get_export_variables (const vfs_path_t *filename_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 186 {
 187     GString *text;
 188     GString *export_vars_string;
 189     size_t i;
 190 
 191     struct
 192     {
 193         const char symbol;
 194         const char *name;
 195         const gboolean is_result_quoted;
 196     } export_variables[] = {
 197         { 'p', "MC_EXT_BASENAME", FALSE },
 198         { 'd', "MC_EXT_CURRENTDIR", FALSE },
 199         { 's', "MC_EXT_SELECTED", TRUE },
 200         { 't', "MC_EXT_ONLYTAGGED", TRUE },
 201         { '\0', NULL, FALSE },
 202     };
 203 
 204     text = exec_get_file_name (filename_vpath);
 205     if (text == NULL)
 206         return NULL;
 207 
 208     export_vars_string = g_string_new ("MC_EXT_FILENAME=");
 209     mc_g_string_concat (export_vars_string, text);
 210     g_string_append (export_vars_string, "\nexport MC_EXT_FILENAME\n");
 211     g_string_free (text, TRUE);
 212 
 213     for (i = 0; export_variables[i].name != NULL; i++)
 214     {
 215         text =
 216             exec_expand_format (export_variables[i].symbol, export_variables[i].is_result_quoted);
 217         if (text != NULL)
 218         {
 219             g_string_append (export_vars_string, export_variables[i].name);
 220             g_string_append_c (export_vars_string, '=');
 221             mc_g_string_concat (export_vars_string, text);
 222             g_string_append (export_vars_string, "\nexport ");
 223             g_string_append (export_vars_string, export_variables[i].name);
 224             g_string_append_c (export_vars_string, '\n');
 225             g_string_free (text, TRUE);
 226         }
 227     }
 228 
 229     return export_vars_string;
 230 }
 231 
 232 /* --------------------------------------------------------------------------------------------- */
 233 
 234 MC_TESTABLE GString *
 235 exec_make_shell_string (const char *lc_data, const vfs_path_t *filename_vpath, GString **buffer)
     /* [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 
 301                         if (*buffer == NULL)
 302                             *buffer = g_string_sized_new (128);
 303                         else
 304                             g_string_set_size (*buffer, 0);
 305 
 306                         lc_data += i - 1;
 307                     }
 308                     else
 309                     {
 310                         char *v;
 311 
 312                         i = check_format_var (lc_data, &v);
 313                         if (i > 0)
 314                         {
 315                             g_string_append (shell_string, v);
 316                             g_free (v);
 317                             lc_data += i;
 318                         }
 319                         else
 320                         {
 321                             GString *text;
 322 
 323                             if (*lc_data != 'f')
 324                                 text = expand_format (NULL, *lc_data, !is_cd);
 325                             else
 326                             {
 327                                 text = exec_get_file_name (filename_vpath);
 328                                 if (text == NULL)
 329                                 {
 330                                     g_string_free (shell_string, TRUE);
 331                                     return NULL;
 332                                 }
 333                             }
 334 
 335                             if (text != NULL)
 336                             {
 337                                 if (!is_cd)
 338                                 {
 339                                     mc_g_string_concat (shell_string, text);
 340                                     g_string_free (text, TRUE);
 341                                 }
 342                                 else if (*buffer == NULL)
 343                                     *buffer = text;
 344                                 else
 345                                 {
 346                                     mc_g_string_concat (*buffer, text);
 347                                     g_string_free (text, TRUE);
 348                                 }
 349                             }
 350 
 351                             written_nonspace = TRUE;
 352                         }
 353                     }
 354                 }
 355             }
 356         }
 357         else if (*lc_data == '%')
 358             expand_prefix_found = TRUE;
 359         else
 360         {
 361             if (!whitespace (*lc_data))
 362                 written_nonspace = TRUE;
 363             if (is_cd)
 364                 g_string_append_c (*buffer, *lc_data);
 365             else
 366                 g_string_append_c (shell_string, *lc_data);
 367         }
 368     }  // for
 369 
 370     return shell_string;
 371 }
 372 
 373 /* --------------------------------------------------------------------------------------------- */
 374 
 375 static void
 376 exec_extension_view (void *target, char *cmd, const vfs_path_t *filename_vpath, int start_line)
     /* [previous][next][first][last][top][bottom][index][help]  */
 377 {
 378     mcview_mode_flags_t def_flags = {
 379         .wrap = FALSE,
 380         .hex = mcview_global_flags.hex,
 381         .magic = FALSE,
 382         .nroff = mcview_global_flags.nroff,
 383     };
 384 
 385     mcview_mode_flags_t changed_flags;
 386 
 387     mcview_clear_mode_flags (&changed_flags);
 388     mcview_altered_flags.hex = FALSE;
 389     mcview_altered_flags.nroff = FALSE;
 390     if (def_flags.hex != mcview_global_flags.hex)
 391         changed_flags.hex = TRUE;
 392     if (def_flags.nroff != mcview_global_flags.nroff)
 393         changed_flags.nroff = TRUE;
 394 
 395     if (target == NULL)
 396         mcview_viewer (cmd, filename_vpath, start_line, 0, 0);
 397     else
 398         mcview_load ((WView *) target, cmd, vfs_path_as_str (filename_vpath), start_line, 0, 0);
 399 
 400     if (changed_flags.hex && !mcview_altered_flags.hex)
 401         mcview_global_flags.hex = def_flags.hex;
 402     if (changed_flags.nroff && !mcview_altered_flags.nroff)
 403         mcview_global_flags.nroff = def_flags.nroff;
 404 
 405     dialog_switch_process_pending ();
 406 }
 407 
 408 /* --------------------------------------------------------------------------------------------- */
 409 
 410 static void
 411 exec_extension_cd (WPanel *panel, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 412 {
 413     size_t i;
 414     vfs_path_t *p_vpath;
 415 
 416     /* Search last non-space character. Start search at the end in order
 417        not to short filenames containing spaces. */
 418     for (i = buffer->len; i != 0 && whitespace (buffer->str[i - 1]); i--)
 419         ;
 420     g_string_set_size (buffer, i);
 421 
 422     p_vpath = vfs_path_from_str_flags (buffer->str, VPF_NO_CANON);
 423     panel_cd (panel, p_vpath, cd_parse_command);
 424     vfs_path_free (p_vpath, TRUE);
 425 }
 426 
 427 /* --------------------------------------------------------------------------------------------- */
 428 
 429 static vfs_path_t *
 430 exec_extension (WPanel *panel, void *target, const vfs_path_t *filename_vpath, const char *lc_data,
     /* [previous][next][first][last][top][bottom][index][help]  */
 431                 int start_line)
 432 {
 433     GString *shell_string, *export_variables;
 434     GString *buffer = NULL;
 435     vfs_path_t *script_vpath = NULL;
 436     int cmd_file_fd;
 437     FILE *cmd_file;
 438     char *cmd = NULL;
 439 
 440     localmtime = 0;
 441     quote_func = name_quote;
 442     run_view = FALSE;
 443     is_cd = FALSE;
 444     written_nonspace = FALSE;
 445 
 446     // Avoid making a local copy if we are doing a cd
 447     do_local_copy = !vfs_file_is_local (filename_vpath);
 448 
 449     shell_string = exec_make_shell_string (lc_data, filename_vpath, &buffer);
 450     if (shell_string == NULL)
 451         goto ret;
 452 
 453     if (is_cd)
 454     {
 455         exec_extension_cd (panel, buffer);
 456         g_string_free (shell_string, TRUE);
 457         goto ret;
 458     }
 459 
 460     /*
 461      * All commands should be run in /bin/sh regardless of user shell.
 462      * To do that, create temporary shell script and run it.
 463      * Sometimes it's not needed (e.g. for %cd and %view commands),
 464      * but it's easier to create it anyway.
 465      */
 466     cmd_file_fd = mc_mkstemps (&script_vpath, "mcext", SCRIPT_SUFFIX);
 467 
 468     if (cmd_file_fd == -1)
 469     {
 470         file_error_message (_ ("Cannot create temporary command file"), NULL);
 471         g_string_free (shell_string, TRUE);
 472         goto ret;
 473     }
 474 
 475     cmd_file = fdopen (cmd_file_fd, "w");
 476     fputs ("#! /bin/sh\n\n", cmd_file);
 477 
 478     export_variables = exec_get_export_variables (filename_vpath);
 479     if (export_variables != NULL)
 480     {
 481         fputs (export_variables->str, cmd_file);
 482         g_string_free (export_variables, TRUE);
 483     }
 484 
 485     fputs (shell_string->str, cmd_file);
 486     g_string_free (shell_string, TRUE);
 487 
 488     /*
 489      * Make the script remove itself when it finishes.
 490      * Don't do it for the viewer - it may need to rerun the script,
 491      * so we clean up after calling view().
 492      */
 493     if (!run_view)
 494         fprintf (cmd_file, "\n/bin/rm -f %s\n", vfs_path_as_str (script_vpath));
 495 
 496     fclose (cmd_file);
 497 
 498     if ((run_view && !written_nonspace) || is_cd)
 499     {
 500         exec_cleanup_script (script_vpath);
 501         script_vpath = NULL;
 502     }
 503     else
 504     {
 505         // Set executable flag on the command file ...
 506         mc_chmod (script_vpath, S_IRWXU);
 507         // ... but don't rely on it - run /bin/sh explicitly
 508         cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (script_vpath), (char *) NULL);
 509     }
 510 
 511     if (run_view)
 512     {
 513         // If we've written whitespace only, then just load filename into view
 514         if (!written_nonspace)
 515             exec_extension_view (target, NULL, filename_vpath, start_line);
 516         else
 517             exec_extension_view (target, cmd, filename_vpath, start_line);
 518     }
 519     else
 520     {
 521         shell_execute (cmd, EXECUTE_INTERNAL);
 522         if (mc_global.tty.console_flag != '\0')
 523         {
 524             handle_console (CONSOLE_SAVE);
 525             if (output_lines != 0 && mc_global.keybar_visible)
 526             {
 527                 unsigned char end_line;
 528 
 529                 end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
 530                 show_console_contents (output_start_y, end_line - output_lines, end_line);
 531             }
 532         }
 533     }
 534 
 535     g_free (cmd);
 536 
 537     exec_cleanup_file_name (filename_vpath, TRUE);
 538 ret:
 539     if (buffer != NULL)
 540         g_string_free (buffer, TRUE);
 541 
 542     return script_vpath;
 543 }
 544 
 545 /* --------------------------------------------------------------------------------------------- */
 546 /**
 547  * Run cmd_file with args, put result into buf.
 548  * If error, put '\0' into buf[0]
 549  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 550  *
 551  * NOTES: buf is null-terminated string.
 552  */
 553 
 554 #ifdef USE_FILE_CMD
 555 static int
 556 get_popen_information (const char *cmd_file, const char *args, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 557 {
 558     gboolean read_bytes = FALSE;
 559     char *command;
 560     FILE *f;
 561 
 562     command = g_strconcat (cmd_file, args, " 2>/dev/null", (char *) NULL);
 563     f = popen (command, "r");
 564     g_free (command);
 565 
 566     if (f != NULL)
 567     {
 568 #ifdef __QNXNTO__
 569         if (setvbuf (f, NULL, _IOFBF, 0) != 0)
 570         {
 571             (void) pclose (f);
 572             return -1;
 573         }
 574 #endif
 575         read_bytes = (fgets (buf, buflen, f) != NULL);
 576         if (!read_bytes)
 577             buf[0] = '\0';  // Paranoid termination
 578         pclose (f);
 579     }
 580     else
 581     {
 582         buf[0] = '\0';  // Paranoid termination
 583         return -1;
 584     }
 585 
 586     buf[buflen - 1] = '\0';
 587 
 588     return read_bytes ? 1 : 0;
 589 }
 590 
 591 /* --------------------------------------------------------------------------------------------- */
 592 /**
 593  * Run the "file" command on the local file.
 594  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 595  */
 596 
 597 static int
 598 get_file_type_local (const vfs_path_t *filename_vpath, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 599 {
 600     GString *filename_quoted;
 601     int ret = 0;
 602 
 603     filename_quoted = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
 604     if (filename_quoted != NULL)
 605     {
 606         ret = get_popen_information (FILE_CMD, filename_quoted->str, buf, buflen);
 607         g_string_free (filename_quoted, TRUE);
 608     }
 609 
 610     return ret;
 611 }
 612 
 613 /* --------------------------------------------------------------------------------------------- */
 614 /**
 615  * Run the "enca" command on the local file.
 616  * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
 617  */
 618 
 619 static int
 620 get_file_encoding_local (const vfs_path_t *filename_vpath, char *buf, int buflen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 621 {
 622     GString *filename_quoted;
 623     int ret = 0;
 624 
 625     filename_quoted = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
 626     if (filename_quoted != NULL)
 627     {
 628         GString *lang;
 629 
 630         lang = name_quote (autodetect_codeset, FALSE);
 631         if (lang != NULL)
 632         {
 633             char *args;
 634 
 635             args = g_strdup_printf (" -L %s -i %s", lang->str, filename_quoted->str);
 636             g_string_free (lang, TRUE);
 637 
 638             ret = get_popen_information ("enca", args, buf, buflen);
 639             g_free (args);
 640         }
 641 
 642         g_string_free (filename_quoted, TRUE);
 643     }
 644 
 645     return ret;
 646 }
 647 
 648 /* --------------------------------------------------------------------------------------------- */
 649 /**
 650  * Invoke the "file" command on the file and match its output against PTR.
 651  * have_type is a flag that is set if we already have tried to determine
 652  * the type of that file.
 653  * Return TRUE for match, FALSE otherwise.
 654  */
 655 
 656 static gboolean
 657 regex_check_type (const vfs_path_t *filename_vpath, const char *ptr, gboolean case_insense,
     /* [previous][next][first][last][top][bottom][index][help]  */
 658                   gboolean *have_type, GError **mcerror)
 659 {
 660     gboolean found = FALSE;
 661 
 662     // Following variables are valid if *have_type is TRUE
 663     static char content_string[BUF_2K];
 664     static size_t content_shift = 0;
 665     static int got_data = 0;
 666 
 667     mc_return_val_if_error (mcerror, FALSE);
 668 
 669     if (!*have_type)
 670     {
 671         vfs_path_t *localfile_vpath;
 672 
 673         static char encoding_id[21];  // CSISO51INISCYRILLIC -- 20
 674         int got_encoding_data;
 675 
 676         // Don't repeate even unsuccessful checks
 677         *have_type = TRUE;
 678 
 679         localfile_vpath = mc_getlocalcopy (filename_vpath);
 680         if (localfile_vpath == NULL)
 681         {
 682             mc_propagate_error (mcerror, 0, _ ("Cannot fetch a local copy of %s"),
 683                                 vfs_path_as_str (filename_vpath));
 684             return FALSE;
 685         }
 686 
 687         got_encoding_data = is_autodetect_codeset_enabled
 688             ? get_file_encoding_local (localfile_vpath, encoding_id, sizeof (encoding_id))
 689             : 0;
 690 
 691         if (got_encoding_data > 0)
 692         {
 693             char *pp;
 694             int cp_id;
 695 
 696             pp = strchr (encoding_id, '\n');
 697             if (pp != NULL)
 698                 *pp = '\0';
 699 
 700             cp_id = get_codepage_index (encoding_id);
 701             if (cp_id == -1)
 702                 cp_id = default_source_codepage;
 703 
 704             do_set_codepage (cp_id);
 705         }
 706 
 707         got_data = get_file_type_local (localfile_vpath, content_string, sizeof (content_string));
 708 
 709         mc_ungetlocalcopy (filename_vpath, localfile_vpath, FALSE);
 710 
 711         if (got_data > 0)
 712         {
 713             char *pp;
 714 
 715             pp = strchr (content_string, '\n');
 716             if (pp != NULL)
 717                 *pp = '\0';
 718 
 719 #ifndef FILE_B
 720             {
 721                 const char *real_name;  // name used with "file"
 722                 size_t real_len;
 723 
 724                 real_name = vfs_path_get_last_path_str (localfile_vpath);
 725                 real_len = strlen (real_name);
 726 
 727                 if (strncmp (content_string, real_name, real_len) == 0)
 728                 {
 729                     // Skip "real_name: "
 730                     content_shift = real_len;
 731 
 732                     // Solaris' file prints tab(s) after ':'
 733                     if (content_string[content_shift] == ':')
 734                         for (content_shift++; whitespace (content_string[content_shift]);
 735                              content_shift++)
 736                             ;
 737                 }
 738             }
 739 #endif
 740         }
 741         else
 742         {
 743             // No data
 744             content_string[0] = '\0';
 745         }
 746         vfs_path_free (localfile_vpath, TRUE);
 747     }
 748 
 749     if (got_data == -1)
 750     {
 751         mc_propagate_error (mcerror, 0, "%s", _ ("Pipe failed"));
 752         return FALSE;
 753     }
 754 
 755     if (content_string[0] != '\0')
 756     {
 757         mc_search_t *search;
 758 
 759         search = mc_search_new (ptr, NULL);
 760         if (search != NULL)
 761         {
 762             search->search_type = MC_SEARCH_T_REGEX;
 763             search->is_case_sensitive = !case_insense;
 764             found = mc_search_run (search, content_string + content_shift, 0,
 765                                    sizeof (content_string) - 1, NULL);
 766             mc_search_free (search);
 767         }
 768         else
 769         {
 770             mc_propagate_error (mcerror, 0, "%s", _ ("Regular expression error"));
 771         }
 772     }
 773 
 774     return found;
 775 }
 776 #endif
 777 
 778 /* --------------------------------------------------------------------------------------------- */
 779 
 780 static void
 781 check_old_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 782 {
 783     char *extension_old_file;
 784 
 785     extension_old_file = mc_config_get_full_path (MC_EXT_OLD_FILE);
 786     if (exist_file (extension_old_file))
 787         message (D_ERROR, _ ("Warning"),
 788                  _ ("You have an outdated %s file.\n%s now uses %s file.\n"
 789                     "Please copy your modifications of the old file to the new one."),
 790                  extension_old_file, PACKAGE_NAME, MC_EXT_FILE);
 791     g_free (extension_old_file);
 792 }
 793 
 794 /* --------------------------------------------------------------------------------------------- */
 795 
 796 static gboolean
 797 load_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 798 {
 799     char *extension_file;
 800     gboolean mc_user_ext = TRUE;
 801     gboolean home_error = FALSE;
 802 
 803     extension_file = mc_config_get_full_path (MC_EXT_FILE);
 804     if (!exist_file (extension_file))
 805     {
 806         g_free (extension_file);
 807 
 808         check_old_extension_file ();
 809 
 810     check_stock_mc_ext:
 811         extension_file = mc_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
 812         if (!exist_file (extension_file))
 813         {
 814             g_free (extension_file);
 815             extension_file =
 816                 mc_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
 817             if (!exist_file (extension_file))
 818                 MC_PTR_FREE (extension_file);
 819         }
 820         mc_user_ext = FALSE;
 821     }
 822 
 823     if (extension_file != NULL)
 824     {
 825         ext_ini = mc_config_init (extension_file, TRUE);
 826         g_free (extension_file);
 827     }
 828     if (ext_ini == NULL)
 829         return FALSE;
 830 
 831     // Check version
 832     if (!mc_config_has_group (ext_ini, descr_group))
 833     {
 834         flush_extension_file ();
 835 
 836         if (!mc_user_ext)
 837         {
 838             message (D_ERROR, MSG_ERROR,
 839                      _ ("The format of the\n%s%s\nfile has changed with version 4.0.\n"
 840                         "It seems that the installation has failed.\nPlease fetch a fresh copy "
 841                         "from the %s package."),
 842                      mc_global.sysconfig_dir, MC_EXT_FILE, PACKAGE_NAME);
 843             return FALSE;
 844         }
 845 
 846         home_error = TRUE;
 847         goto check_stock_mc_ext;
 848     }
 849 
 850     if (home_error)
 851     {
 852         extension_file = mc_config_get_full_path (MC_EXT_FILE);
 853         message (
 854             D_ERROR, MSG_ERROR,
 855             _ ("The format of the\n%s\nfile has changed with version 4.0.\nYou may either want "
 856                "to copy it from\n%s%s\nor use that file as an example of how to write it."),
 857             extension_file, mc_global.sysconfig_dir, MC_EXT_FILE);
 858         g_free (extension_file);
 859     }
 860 
 861     return TRUE;
 862 }
 863 
 864 /* --------------------------------------------------------------------------------------------- */
 865 /*** public functions ****************************************************************************/
 866 /* --------------------------------------------------------------------------------------------- */
 867 
 868 void
 869 flush_extension_file (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 870 {
 871     g_strfreev (ext_ini_groups);
 872     ext_ini_groups = NULL;
 873 
 874     mc_config_deinit (ext_ini);
 875     ext_ini = NULL;
 876 }
 877 
 878 /* --------------------------------------------------------------------------------------------- */
 879 /**
 880  * The second argument is action, i.e. Open, View or Edit
 881  * Use target object to open file in.
 882  *
 883  * This function returns:
 884  *
 885  * -1 for a failure or user interrupt
 886  * 0 if no command was run
 887  * 1 if some command was run
 888  *
 889  * If action == "View" then a parameter is checked in the form of "View:%d",
 890  * if the value for %d exists, then the viewer is started up at that line number.
 891  */
 892 
 893 int
 894 regex_command_for (void *target, const vfs_path_t *filename_vpath, const char *action,
     /* [previous][next][first][last][top][bottom][index][help]  */
 895                    vfs_path_t **script_vpath)
 896 {
 897     const char *filename;
 898     size_t filename_len;
 899     gboolean found = FALSE;
 900     gboolean error_flag = FALSE;
 901     int ret = 0;
 902     struct stat mystat;
 903     int view_at_line_number = 0;
 904 #ifdef USE_FILE_CMD
 905     gboolean have_type = FALSE;  // Flag used by regex_check_type()
 906 #endif
 907     char **group_iter;
 908     char *include_group = NULL;
 909     const char *current_group;
 910 
 911     if (filename_vpath == NULL)
 912         return 0;
 913 
 914     if (script_vpath != NULL)
 915         *script_vpath = NULL;
 916 
 917     // Check for the special View:%d parameter
 918     if (strncmp (action, "View:", 5) == 0)
 919     {
 920         view_at_line_number = atoi (action + 5);
 921         action = "View";
 922     }
 923 
 924     if (ext_ini == NULL && !load_extension_file ())
 925         return 0;
 926 
 927     mc_stat (filename_vpath, &mystat);
 928 
 929     filename = vfs_path_get_last_path_str (filename_vpath);
 930     filename = x_basename (filename);
 931     filename_len = strlen (filename);
 932 
 933     if (ext_ini_groups == NULL)
 934         ext_ini_groups = mc_config_get_groups (ext_ini, NULL);
 935 
 936     // find matched type, regex or shell pattern
 937     for (group_iter = ext_ini_groups; *group_iter != NULL && !found; group_iter++)
 938     {
 939         enum
 940         {
 941             TYPE_UNUSED,
 942             TYPE_NOT_FOUND,
 943             TYPE_FOUND
 944         } type_state = TYPE_UNUSED;
 945 
 946         const gchar *g = *group_iter;
 947         gchar *pattern;
 948         gboolean ignore_case;
 949 
 950         if (strcmp (g, descr_group) == 0 || strncmp (g, "Include/", 8) == 0
 951             || strcmp (g, default_group) == 0)
 952             continue;
 953 
 954         /* The "Directory" parameter is a special case: if it's present then
 955            "Type", "Regex", and "Shell" parameters are ignored */
 956         pattern = mc_config_get_string_raw (ext_ini, g, "Directory", NULL);
 957         if (pattern != NULL)
 958         {
 959             found = S_ISDIR (mystat.st_mode)
 960                 && mc_search (pattern, NULL, vfs_path_as_str (filename_vpath), 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, NULL);
 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]  */