/* Extension dependent execution. Copyright (C) 1994, 1995 The Free Software Foundation Written by: 1995 Jakub Jelinek 1994 Miguel de Icaza This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #include "global.h" #include "tty.h" #include "user.h" #include "main.h" #include "dialog.h" #include "ext.h" #include "view.h" #include "main.h" #include "../vfs/vfs.h" #include "cons.saver.h" #include "layout.h" /* If set, we execute the file command to check the file type */ int use_file_to_check_type = 1; /* This variable points to a copy of the mc.ext file in memory * With this we avoid loading/parsing the file each time we * need it */ static char *data = NULL; void flush_extension_file (void) { if (data){ g_free (data); data = NULL; } } typedef char *(*quote_func_t)(const char *name, int i); static char * quote_block (quote_func_t quote_func, char **quoting_block) { char **p; char *result; char *tail; int tail_index; int current_len; result = NULL; current_len = 0; tail_index = 0; for (p = quoting_block; *p; p++) { int temp_len; char *temp; temp = quote_func (*p, FALSE); temp_len = strlen (temp); current_len += temp_len + 2; result = g_realloc (result, current_len); tail = result + tail_index; strcpy (tail, temp); tail[temp_len] = ' '; tail[temp_len + 1] = '\0'; tail_index += temp_len + 1; g_free (temp); } return result; } void exec_extension (const char *filename, const char *data, char **drops, int *move_dir, int start_line, int needs_term) { char *file_name; int cmd_file_fd; FILE *cmd_file; int expand_prefix_found = 0; int parameter_found = 0; char prompt [80]; int run_view = 0; int def_hex_mode = default_hex_mode, changed_hex_mode = 0; int def_nroff_flag = default_nroff_flag, changed_nroff_flag = 0; int written_nonspace = 0; int is_cd = 0; char buffer [1024]; char *p = 0; char *localcopy = NULL; int do_local_copy; time_t localmtime = 0; struct stat mystat; quote_func_t quote_func = name_quote; g_return_if_fail (filename != NULL); g_return_if_fail (data != NULL); /* Avoid making a local copy if we are doing a cd */ if (!vfs_file_is_local(filename)) do_local_copy = 1; else do_local_copy = 0; /* * All commands should be run in /bin/sh regardless of user shell. * To do that, create temporary shell script and run it. * Sometimes it's not needed (e.g. for %cd and %view commands), * but it's easier to create it anyway. */ cmd_file_fd = mc_mkstemps(&file_name, "mcext", SCRIPT_SUFFIX); if (cmd_file_fd == -1){ message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "), unix_error_string (errno)); return; } cmd_file = fdopen (cmd_file_fd, "w"); fputs ("#!/bin/sh\n", cmd_file); prompt [0] = 0; for (;*data && *data != '\n'; data++){ if (parameter_found){ if (*data == '}'){ char *parameter; parameter_found = 0; parameter = input_dialog (_(" Parameter "), prompt, ""); if (!parameter){ /* User canceled */ fclose (cmd_file); unlink (file_name); if (localcopy) { mc_ungetlocalcopy (filename, localcopy, 0); } g_free (file_name); return; } fputs (parameter, cmd_file); written_nonspace = 1; g_free (parameter); } else { int len = strlen (prompt); if (len < sizeof (prompt) - 1){ prompt [len] = *data; prompt [len+1] = 0; } } } else if (expand_prefix_found){ expand_prefix_found = 0; if (*data == '{') parameter_found = 1; else { int i = check_format_view (data); char *v; if (i){ data += i - 1; run_view = 1; } else if ((i = check_format_cd (data)) > 0) { is_cd = 1; quote_func = fake_name_quote; do_local_copy = 0; p = buffer; data += i - 1; } else if ((i = check_format_var (data, &v)) > 0 && v){ fputs (v, cmd_file); g_free (v); data += i; } else { char *text; if (*data == 'f'){ if (do_local_copy){ localcopy = mc_getlocalcopy (filename); if (localcopy == NULL) { fclose(cmd_file); unlink(file_name); g_free (file_name); return; } mc_stat (localcopy, &mystat); localmtime = mystat.st_mtime; text = (*quote_func) (localcopy, 0); } else { text = (*quote_func) (filename, 0); } } else if (*data == 'q') { text = quote_block (quote_func, drops); } else text = expand_format (NULL, *data, !is_cd); if (!is_cd) fputs (text, cmd_file); else { strcpy (p, text); p = strchr (p, 0); } g_free (text); written_nonspace = 1; } } } else { if (*data == '%') expand_prefix_found = 1; else { if (*data != ' ' && *data != '\t') written_nonspace = 1; if (is_cd) *(p++) = *data; else fputc (*data, cmd_file); } } } /* for */ /* Make sure that the file removes itself when it finishes */ fprintf (cmd_file, "\n/bin/rm -f %s\n", file_name); fclose (cmd_file); if ((run_view && !written_nonspace) || is_cd) { unlink (file_name); g_free (file_name); file_name = NULL; } else { chmod (file_name, S_IRWXU); } if (run_view){ altered_hex_mode = 0; altered_nroff_flag = 0; if (def_hex_mode != default_hex_mode) changed_hex_mode = 1; if (def_nroff_flag != default_nroff_flag) changed_nroff_flag = 1; /* If we've written whitespace only, then just load filename * into view */ if (written_nonspace) view (file_name, filename, move_dir, start_line); else view (0, filename, move_dir, start_line); if (changed_hex_mode && !altered_hex_mode) default_hex_mode = def_hex_mode; if (changed_nroff_flag && !altered_nroff_flag) default_nroff_flag = def_nroff_flag; repaint_screen (); } else if (is_cd) { char *q; *p = 0; p = buffer; while (*p == ' ' && *p == '\t') p++; /* Search last non-space character. Start search at the end in order not to short filenames containing spaces. */ q = p + strlen (p) - 1; while (q >= p && (*q == ' ' || *q == '\t')) q--; q[1] = 0; do_cd (p, cd_parse_command); } else { shell_execute (file_name, EXECUTE_INTERNAL | EXECUTE_TEMPFILE); if (console_flag) { handle_console (CONSOLE_SAVE); if (output_lines && keybar_visible) { show_console_contents (output_start_y, LINES-keybar_visible-output_lines-1, LINES-keybar_visible-1); } } } if (file_name) { g_free (file_name); } if (localcopy) { mc_stat (localcopy, &mystat); mc_ungetlocalcopy (filename, localcopy, localmtime != mystat.st_mtime); } } #ifdef FILE_L # define FILE_CMD "file -L " #else # define FILE_CMD "file " #endif /* The second argument is action, i.e. Open, View, Edit, Drop, or NULL if * we want regex_command to return a list of all user defined actions. * Third argument is space separated list of dropped files (for actions * other then Drop it should be NULL); * * This function returns: * * If action != NULL, then it returns "Success" (not allocated) if it ran * some command or NULL if not. * * If action == NULL, it returns NULL if there are no user defined commands * or an allocated space separated list of user defined Actions. * * If action == "Icon", we are doing again something special. We return * icon name and we set the variable regex_command_title to Title for * that icon. * * If action == "View" then a parameter is checked in the form of "View:%d", * if the value for %d exists, then the viewer is started up at that line number. */ char *regex_command_title = NULL; char *regex_command (char *filename, char *action, char **drops, int *move_dir) { char *p, *q, *r, c; int file_len = strlen (filename); int found = 0; char content_string [2048]; int content_shift = 0; char *to_return = NULL; int old_patterns; struct stat mystat; int asked_file; int view_at_line_number; char *include_target; int include_target_len; #ifdef FILE_STDIN int file_supports_stdin = 1; #else int file_supports_stdin = 0; #endif /* Check for the special View:%d parameter */ if (action && strncmp (action, "View:", 5) == 0){ view_at_line_number = atoi (action + 5); action [4] = 0; } else { view_at_line_number = 0; } /* Have we asked file for the file contents? */ asked_file = 0; if (data == NULL) { char *extension_file; int mc_user_ext = 1; int home_error = 0; extension_file = concat_dir_and_file (home_dir, MC_USER_EXT); if (!exist_file (extension_file)) { g_free (extension_file); check_stock_mc_ext: extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT); mc_user_ext = 0; } data = load_file (extension_file); g_free (extension_file); if (data == NULL) return 0; if (!strstr (data, "default/")) { if (!strstr (data, "regex/") && !strstr (data, "shell/") && !strstr (data, "type/")) { g_free (data); data = NULL; if (mc_user_ext) { home_error = 1; goto check_stock_mc_ext; } else { char *msg; char *msg2; msg = g_strconcat (" ", mc_home, MC_LIB_EXT, _(" file error"), NULL); msg2 = g_strconcat (_("Format of the "), mc_home, _("mc.ext file has changed\n\ with version 3.0. It seems that installation\n\ failed. Please fetch a fresh new copy from the\n\ Midnight Commander package."), NULL); message (1, msg, msg2); g_free (msg); g_free (msg2); return 0; } } } if (home_error) { char *msg; char *msg2; msg = g_strconcat (" ~/", MC_USER_EXT, _(" file error "), NULL); msg2 = g_strconcat (_("Format of the ~/"), MC_USER_EXT, _(" file has changed\n\ with version 3.0. You may want either to\n\ copy it from "), mc_home, _("mc.ext or use that\n\ file as an example of how to write it.\n\ "), mc_home, _("mc.ext will be used for this moment."), NULL); message (1, msg, msg2); g_free (msg); g_free (msg2); } } mc_stat (filename, &mystat); if (regex_command_title){ g_free (regex_command_title); regex_command_title = NULL; } old_patterns = easy_patterns; easy_patterns = 0; /* Real regular expressions are needed :) */ include_target = NULL; include_target_len = 0; for (p = data; *p; p++) { for (q = p; *q == ' ' || *q == '\t'; q++) ; if (*q == '\n' || !*q) p = q; /* empty line */ if (*p == '#') /* comment */ while (*p && *p != '\n') p++; if (*p == '\n') continue; if (!*p) break; if (p == q) { /* i.e. starts in the first column, should be * keyword/descNL */ if (found && action == NULL) /* We have already accumulated all * the user actions */ break; found = 0; q = strchr (p, '\n'); if (q == NULL) q = strchr (p, 0); c = *q; *q = 0; if (include_target){ if ((strncmp (p, "include/", 8) == 0) && (strncmp (p+8, include_target, include_target_len) == 0)) found = 1; } else if (!strncmp (p, "regex/", 6)) { p += 6; /* Do not transform shell patterns, you can use shell/ for * that */ if (regexp_match (p, filename, match_normal)) found = 1; } else if (!strncmp (p, "directory/", 10)) { if (S_ISDIR (mystat.st_mode) && regexp_match (p+10, filename, match_normal)) found = 1; } else if (!strncmp (p, "shell/", 6)) { p += 6; if (*p == '.') { if (!strncmp (p, filename + file_len - (q - p), q - p)) found = 1; } else { if (q - p == file_len && !strncmp (p, filename, q - p)) found = 1; } } else if (!strncmp (p, "type/", 5)) { int islocal = vfs_file_is_local (filename); p += 5; if (islocal || file_supports_stdin) { char *pp; int hasread = use_file_to_check_type; if (asked_file || !use_file_to_check_type) goto match_file_output; hasread = 0; if (islocal) { char *tmp = name_quote (filename, 0); char *command = g_strconcat (FILE_CMD, tmp, NULL); FILE *f = popen (command, "r"); g_free (tmp); g_free (command); if (f != NULL) { hasread = (fgets (content_string, 2047, f) != NULL); if (!hasread) content_string [0] = 0; pclose (f); #ifdef SCO_FLAVOR /* ** SCO 3.2 does has a buggy pclose(), so ** become zombie (alex) */ waitpid(-1,NULL,WNOHANG); #endif /* SCO_FLAVOR */ } } else { #ifdef _OS_NT message (1, " Win32 ", " Unimplemented file prediction "); #else int pipehandle, remotehandle; pid_t p; remotehandle = mc_open (filename, O_RDONLY); if (remotehandle != -1) { /* 8192 is HOWMANY hardcoded value in the file-3.14 * sources. Tell me if any other file uses larger * chunk from beginning */ pipehandle = mc_doublepopen (remotehandle, 8192, &p,"file", "file", "-", NULL); if (pipehandle != -1) { int i; while ((i = read (pipehandle, content_string + hasread, 2047 - hasread)) > 0) hasread += i; mc_doublepclose (pipehandle, p); content_string [hasread] = 0; } mc_close (remotehandle); } #endif /* _OS_NT */ } asked_file = 1; match_file_output: if (hasread) { if ((pp = strchr (content_string, '\n')) != 0) *pp = 0; if (islocal && !strncmp (content_string, filename, file_len)) { content_shift = file_len; if (content_string [content_shift] == ':') for (content_shift++; content_string [content_shift] == ' '; content_shift++); } else if (!islocal && !strncmp (content_string, "standard input:", 15)) { for (content_shift = 15; content_string [content_shift] == ' '; content_shift++); } if (content_string && regexp_match (p, content_string + content_shift, match_normal)){ found = 1; } } } } else if (!strncmp (p, "default/", 8)) { p += 8; found = 1; } *q = c; p = q; if (!*p) break; } else { /* List of actions */ p = q; q = strchr (p, '\n'); if (q == NULL) q = strchr (p, 0); if (found) { r = strchr (p, '='); if (r != NULL) { c = *r; *r = 0; if (strcmp (p, "Include") == 0){ char *t; include_target = p + 8; t = strchr (include_target, '\n'); if (t) *t = 0; include_target_len = strlen (include_target); if (t) *t = '\n'; *r = c; p = q; found = 0; if (!*p) break; continue; } if (action == NULL) { if (strcmp (p, "Open") && strcmp (p, "View") && strcmp (p, "Edit") && strcmp (p, "Drop") && strcmp (p, "Icon") && strcmp (p, "Include") && strcmp (p, "Title")) { /* I.e. this is a name of a user defined action */ static char *q; if (to_return == NULL) { to_return = g_malloc (512); q = to_return; } else *(q++) = '='; /* Mark separator */ strcpy (q, p); q = strchr (q, 0); } *r = c; } else if (!strcmp (action, "Icon")) { if (!strcmp (p, "Icon") && to_return == NULL) { *r = c; c = *q; *q = 0; to_return = g_strdup (r + 1); } else if (!strcmp (p, "Title") && regex_command_title == NULL) { *r = c; c = *q; *q = 0; regex_command_title = g_strdup (r + 1); } else { *r = c; c = *q; } *q = c; if (to_return != NULL && regex_command_title != NULL) break; } else if (!strcmp (action, p)) { *r = c; for (p = r + 1; *p == ' ' || *p == '\t'; p++) ; /* Empty commands just stop searching * through, they don't do anything * * We need to copy the filename because exec_extension * may end up invoking update_panels thus making the * filename parameter invalid (ie, most of the time, * we get filename as a pointer from cpanel->dir). */ if (p < q) { char *filename_copy = g_strdup (filename); exec_extension (filename_copy, r + 1, drops, move_dir, view_at_line_number, 0); g_free (filename_copy); to_return = "Success"; } break; } else *r = c; } } p = q; if (!*p) break; } } easy_patterns = old_patterns; return to_return; }