/* 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include <stdio.h>
#include <ctype.h>
#ifdef NEEDS_IO_H
# include <io.h>
#endif

#include "tty.h"
#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#include "global.h"
#include "user.h"
#include "main.h"
#include "dialog.h"
#include "ext.h"
#include "view.h"
#include "main.h"
#include "../vfs/vfs.h"
#include "x.h"

#include "cons.saver.h"
#include "layout.h"
#ifdef SCO_FLAVOR
#include <sys/wait.h>
#endif /* SCO_FLAVOR */

/* 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 = quoting_block;
	char *result = 0;
	char *tail   = 0;
	int current_len = 0;

	for (p = quoting_block; *p; p++){
		int  temp_len;
		char *temp = quote_func (*p, 0);

		temp_len = strlen (temp);
		current_len += temp_len + 2;
		result = g_realloc (result, current_len);
		if (!tail)
			tail = result;
		strcpy (tail, temp);
		strcat (tail, " ");
		tail += 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)
{
    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;
    
    /* Note: this has to be done after the getlocalcopy call,
     * since it uses tmpnam as well
     */
    file_name = g_strdup (tmpnam (NULL));

    /* #warning FIXME: this is ugly */
    if ((cmd_file_fd = open (file_name, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600)) == -1){
	message (1, MSG_ERROR, _(" Can't create temporary command file \n %s "),
		 unix_error_string (errno));
	return;
    }
    cmd_file = fdopen (cmd_file_fd, "w");
    /* FIXME: This is a hack, that makes us sure, we are using the right syntax */
    fprintf (cmd_file, "#!/bin/sh\n");
	     
    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 (*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 */
    fputc ('\n', cmd_file);
    fclose (cmd_file);
    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);
		
	    }
	}
	
#ifdef OLD_CODE
	if (vfs_current_is_local ())
	    shell_execute (file_name, EXECUTE_INTERNAL);
	else
	    message (1, _(" Warning "), _(" Can't execute commands on a Virtual File System directory "));
#endif
    }
#ifndef PORT_DOES_BACKGROUND_EXEC
    unlink (file_name);
#endif
    if (localcopy) {
        mc_stat (localcopy, &mystat);
        mc_ungetlocalcopy (filename, localcopy, localmtime != mystat.st_mtime);
    }
    g_free (file_name);
}

#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 *extension_file;
    char *p, *q, *r, c;
    char *buffer;
    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) {
        int home_error = 0;

        buffer = concat_dir_and_file (home_dir, MC_USER_EXT);
    	if (exist_file (buffer))
	    extension_file = buffer;
        else
check_stock_mc_ext:
	    extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT);
        if ((data = load_file (extension_file)) == NULL) {
	    g_free (buffer);
	    return 0;
	}
	if (!strstr (data, "default/")) {
	    if (!strstr (data, "regex/") && !strstr (data, "shell/") &&
	        !strstr (data, "type/")) {
	        g_free (data);
	        data = NULL;
	        if (extension_file == buffer) {
		    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 or in case you don't\n\
have any, get it from ftp://ftp.nuclecu.unam.mx."), NULL);
	            message (1, msg, msg2);
                    g_free (msg);
                    g_free (msg2);
		    g_free (buffer);
		    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);
        }
        g_free (buffer);
    }
    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;
    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 
	    	    	    **	<command> 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);
			    g_free (filename_copy);
			    
    	                    to_return = "Success";
    	                }
    	                break;
    	            } else
    	            	*r = c;
    	        }
    	    }
    	    p = q;
    	    if (!*p)
    	        break;
    	}
    }
    easy_patterns = old_patterns;
    return to_return;
}
