/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* Please leave outside of ifdef for windows precompiled headers */
#include "xp.h"
#include "prmem.h"
#include "plstr.h"
#include "netutils.h"
#include "mkselect.h"
#include "mktcp.h"
#include "net_xp_file.h"
#include "mkgeturl.h"
#include <signal.h>

#ifdef MOZILLA_CLIENT

#include "mkstream.h"
#include "mkparse.h"
#include "cvview.h"

#include "libmime.h"

/* for select(), fd_set, struct timeval */
#if defined(XP_UNIX)
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#if defined(AIX)
#include <sys/select.h>
#endif
#elif defined(XP_WIN)
#include <windows.h>
#elif defined(XP_MAC)
#include "macsocket.h"
#endif

/* for XP_GetString() */
#include <xpgetstr.h>
extern int XP_CONFIRM_EXEC_UNIXCMD_ARE;
extern int XP_CONFIRM_EXEC_UNIXCMD_MAYBE;
extern int XP_ALERT_UNABLE_INVOKEVIEWER;
extern int MK_UNABLE_TO_OPEN_TMP_FILE;


typedef struct _CV_DataObject {
    FILE      * fp;
    char      * filename;
    char      * command;
	char      * url;
    unsigned int stream_block_size;
	int32       cur_size;
	int32       tot_size;
	MWContext * context;
} CV_DataObject;


/*
** build_viewer_cmd
**	Build up the command for forking the external viewer.
**	Argument list is the template for the command and the a set of
**	(char,char*) pairs of characters to be recognized as '%' escapes
**	and what they should expand to, terminated with a 0.
**
**	Return value is a malloc'ed string, must be freed when command is done.
**
**	Example:
**	    char* s=build_viewer(line_from_mailcap, 's', tmpFile, 'u', url, 0);
**
**	I'm completely unsure what to do about security considerations and
**	encodings.  Should the URL get % encoded.  What if it contains "bad"
**	characters.  etc. etc. etc
*/

char*
build_viewer_cmd(char *template, ...)
{
  va_list args;
  char *ret, *from, *to;
  int len;

  if (template == NULL)
    return NULL;

  len = strlen(template);
  ret = (char*) malloc(len+1);
  if (ret == NULL)
    return NULL;

  from = template, to = ret;

  while (*from) {
    if (*from != '%' || *++from == '%') {
      *to++ = *from++;
    } else {

      /*
      ** We have a % escape, now look through all the arguments for
      ** a matching one.  When one is found, substitute in the
      ** passed value.  If none is found, the % and following character
      ** get swallowed.
      */
      char argc;
      char* argv;

      va_start(args, template);
      while ((argc = va_arg(args, int)) != 0) {
	argv = va_arg(args, char*);
	if (*from == argc) {
	  int off = to - ret;
	  int arglen = strlen(argv);

	  len = len + arglen - 2;
	  ret = (char*) realloc(ret, len + 1);
	  if (ret == NULL)
	    return NULL;
	  PL_strcpy(ret + off, argv);
	  to = ret + off + arglen;
	  break;
	}
      }
      if (*from) from++;	/* skip char following % unless it was last */
      va_end(args);
    }
  }
  *to = '\0';
  return ret;
}

PRIVATE int net_ExtViewWrite (NET_StreamClass *stream, CONST char* s, int32 l)
{
	CV_DataObject *obj=stream->data_object;	
	if(obj->tot_size)
	  {
		obj->cur_size += l;

		obj->context->funcs->SetProgressBarPercent(obj->context, (obj->cur_size*100)/obj->tot_size);
	  }

    /* TRACEMSG(("Length of string passed to display: %d\n",l));
     */
    return(fwrite((char *) s, 1, l, obj->fp)); 
}

PRIVATE int net_ExtViewWriteReady (NET_StreamClass * stream)
{
	CV_DataObject *obj=stream->data_object;
    fd_set write_fds;
    struct timeval timeout;
    int ret;	

    if(obj->command)
	  {
		return(MAX_WRITE_READY);  /* never wait for files */
	  }

    timeout.tv_sec = 0;
    timeout.tv_usec = 1;  /* minimum hopefully */

    memset(&write_fds, 0, sizeof(fd_set));

    FD_SET(fileno(obj->fp), &write_fds);

    ret = select(fileno(obj->fp)+1, NULL, &write_fds, NULL, &timeout);

    if(ret)
	    return(obj->stream_block_size);  /* read in a max of 8000 bytes */
	else
		return(0);
}


PRIVATE void net_ExtViewComplete (NET_StreamClass *stream)
{
	CV_DataObject *obj=stream->data_object;
	obj->context->funcs->SetProgressBarPercent(obj->context, 100);	

    if(obj->command)
	  {
		char *p_tmp;
		char *command;

        /* restrict to allowed url chars
         *
         */
        for(p_tmp = obj->url; *p_tmp != '\0'; p_tmp++)
                if( (*p_tmp >= '0' && *p_tmp <= '9')
                    || (*p_tmp >= 'A' && *p_tmp <= 'Z')
                    || (*p_tmp >= 'a' && *p_tmp <= 'z')
                    || (*p_tmp == '_')
                    || (*p_tmp == '?')
                    || (*p_tmp == '#')
                    || (*p_tmp == '&')
                    || (*p_tmp == '%')
                    || (*p_tmp == '/')
                    || (*p_tmp == ':')
                    || (*p_tmp == '+')
                    || (*p_tmp == '.')
                    || (*p_tmp == '~')
                    || (*p_tmp == '=')
                    || (*p_tmp == '-'))
          {
            /* this is a good character.  Allow it.
             */
          }
        else
          {
            *p_tmp = '\0';
            break;
          }

		command=build_viewer_cmd(obj->command, 
							 's', obj->filename, 
							 'u', obj->url, 0);

        fclose(obj->fp);
		TRACEMSG(("Invoking: %s", command));

        system(command);
	    PR_FREEIF(obj->command);
      }
	else
      {
        pclose(obj->fp);
      }

    PR_FREEIF(obj->filename);
	PR_FREEIF(obj->url);
    PR_Free(obj);
    return;
}

PRIVATE void net_ExtViewAbort (NET_StreamClass *stream, int status)
{
	CV_DataObject *obj=stream->data_object;
	obj->context->funcs->SetProgressBarPercent(obj->context, 100);	

    fclose(obj->fp);
   
    if(obj->filename)
      {
	    remove(obj->filename);
        PR_Free(obj->filename);
      }

	PR_FREEIF(obj->url);
    PR_FREEIF(obj->command);

    PR_Free(obj);
    
    return;
}

#ifdef XP_UNIX
extern char **fe_encoding_extensions; /* gag! */
#endif


PUBLIC NET_StreamClass * 
NET_ExtViewerConverter   (int         format_out,
                          void       *data_obj,
                          URL_Struct *URL_s,
                          MWContext  *window_id)
{
    CV_DataObject* obj;
    NET_StreamClass* stream;
	char *tmp_filename;
    char *dot;
    char *path;
    CV_ExtViewStruct * view_struct = (CV_ExtViewStruct *)data_obj;
	char small_buf[256];
	int yes_stream=0;
    
	/* If this URL is a mail or news attachment, use the name of that
	   attachment as the URL -- this is so the temp file gets the right
	   extension on it (some helper apps are picky about that...)
	 */
	path = MimeGuessURLContentName(window_id, URL_s->address);
	if (!path)
	  path = NET_ParseURL(URL_s->address, GET_PATH_PART);
	if (!path)
	  return 0;


    TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));

    stream = PR_NEW(NET_StreamClass);
    if(stream == NULL) 
            return(NULL);

	memset(stream, 0, sizeof(NET_StreamClass));

    obj = PR_NEW(CV_DataObject);
    if (obj == NULL) 
            return(NULL);
	memset(obj, 0, sizeof(CV_DataObject));

	obj->context  = window_id;

	if(URL_s->content_length)
	  {
	    obj->tot_size = URL_s->content_length;
	  }
	else
	  {
	    /* start the progress bar cyloning
	     */
	    obj->context->funcs->SetProgressBarPercent(window_id, -1);
	  }

    stream->name           = "Execute external viewer";
    stream->complete       = (MKStreamCompleteFunc) net_ExtViewComplete;
    stream->abort          = (MKStreamAbortFunc) net_ExtViewAbort;
    stream->put_block      = (MKStreamWriteFunc) net_ExtViewWrite;
    stream->is_write_ready = (MKStreamWriteReadyFunc) net_ExtViewWriteReady;
    stream->data_object    = obj;  /* document info object */
    stream->window_id      = window_id;

#ifdef XP_UNIX
	/* Some naive people may have trustingly put

			application/x-sh; sh %s
			application/x-csh; csh %s

	   in their mailcap files without realizing how dangerous that is.
	   Worse, it might be there and they might not realize it.  So, if
	   we're about to execute a shell, pop up a dialog box first.
	 */
	{
	  char *prog = PL_strdup (view_struct->system_command);
	  char *s, *start, *end;
	  int danger = 0;

	  /* strip end space */
	  end = XP_StripLine(prog);

	  /* Extract the leaf name of the program: " /bin/sh -foo" ==> "sh". */
	  for (; *end && !NET_IS_SPACE(*end); end++)
		;
	  *end = 0;

	  if ((start = PL_strrchr (prog, '/')))
		start++;
	  else
	    start = XP_StripLine(prog); /* start at first non-white space */

	  /* Strip off everything after the first nonalphabetic.
		 This is so "perl-4.0" is compared as "perl" and
		 "emacs19" is compared as "emacs".
	   */
	  for (s = start; *s; s++)
		if (!isalpha (*s))
		  *s = 0;

	  /* These are things known to be shells - very bad. */
	  if (!PL_strcmp (start, "ash") ||
		  !PL_strcmp (start, "bash") ||
		  !PL_strcmp (start, "csh") ||
		  !PL_strcmp (start, "jsh") ||
		  !PL_strcmp (start, "ksh") ||
		  !PL_strcmp (start, "pdksh") ||
		  !PL_strcmp (start, "sh") ||
		  !PL_strcmp (start, "tclsh") ||
		  !PL_strcmp (start, "tcsh") ||
		  !PL_strcmp (start, "wish") ||		/* a tcl thing */
		  !PL_strcmp (start, "wksh") ||
		  !PL_strcmp (start, "zsh"))
		danger = 2;

	  /* Remote shells are potentially dangerous, in the case of
		 "rsh somehost some-dangerous-program", but it's hard to
		 parse that out, since rsh could take arbitrarily complicated
		 args, like "rsh somehost -u something -pass8 /bin/sh %s".
		 And we don't want to squawk about "rsh somehost playulaw -".
		 So... allow rsh to possibly be a security hole.
	   */
	  else if (!PL_strcmp (start, "remsh") ||	/* remote shell */
			   !PL_strcmp (start, "rksh") ||
			   !PL_strcmp (start, "rsh")		/* remote- or restricted- */
			   )
		danger = 0;

	  /* These are things which aren't really shells, but can do the
		 same damage anyway since they can write files and/or execute
		 other programs. */
	  else if (!PL_strcmp (start, "awk") ||
			   !PL_strcmp (start, "e") ||
			   !PL_strcmp (start, "ed") ||
			   !PL_strcmp (start, "ex") ||
			   !PL_strcmp (start, "gawk") ||
			   !PL_strcmp (start, "m4") ||
			   !PL_strcmp (start, "sed") ||
			   !PL_strcmp (start, "vi") ||
			   !PL_strcmp (start, "emacs") ||
			   !PL_strcmp (start, "lemacs") ||
			   !PL_strcmp (start, "xemacs") ||
			   !PL_strcmp (start, "temacs") ||

			   /* Other dangerous interpreters */
			   !PL_strcmp (start, "basic") ||
			   !PL_strcmp (start, "expect") ||
			   !PL_strcmp (start, "expectk") ||
			   !PL_strcmp (start, "perl") ||
			   !PL_strcmp (start, "python") ||
			   !PL_strcmp (start, "rexx")
			   )
		danger = 1;

	  /* Be suspicious of anything ending in "sh". */
	  else if (PL_strlen (start) > 2 &&
			   !PL_strcmp (start + PL_strlen (start) - 2, "sh"))
		danger = 1;

	  if (danger)
		{
		  char msg [2048];
		  PR_snprintf (msg,
				   sizeof(msg),
				   (danger > 1 ? XP_GetString(XP_CONFIRM_EXEC_UNIXCMD_ARE) : XP_GetString(XP_CONFIRM_EXEC_UNIXCMD_MAYBE)),
				   start
				   );
		  if (!FE_Confirm (window_id, msg))
			{
			  PR_Free (stream);
			  PR_Free (obj);
			  PR_Free (path);
			  PR_Free (prog);
			  return(NULL);
			}
		}
	  PR_Free (prog);
	}
#endif /* XP_UNIX */

    if(view_struct->stream_block_size)
	  {
		/* asks the user if they want to stream data.
		 * -1 cancel
		 * 0  No, don't stream data, play from the file
		 * 1  Yes, stream the data from the network
		 */
		if (NET_URL_Type (URL_s->address) == ABOUT_TYPE_URL)
		  yes_stream = 1;
		else
		  yes_stream = FE_AskStreamQuestion(window_id);

		if(yes_stream == -1)
		  {
			PR_Free(stream);
			PR_Free(obj);
        	PR_Free(path);
			return(NULL);
		  }
	  }

    if(yes_stream && view_struct->stream_block_size)
      {
		/* use popen */
		obj->fp = popen(view_struct->system_command, "w");

		if(!obj->fp)
		  {
			FE_Alert(window_id, XP_GetString(XP_ALERT_UNABLE_INVOKEVIEWER));
		    return(NULL);
		  }

		obj->stream_block_size = view_struct->stream_block_size;

		signal(SIGPIPE, SIG_IGN);

      }
	else
      {
		
		dot = PL_strrchr(path, '.');

#ifdef XP_UNIX
		/* Gag.  foo.ps.gz --> tmpXXXXX.ps, not tmpXXXXX.gz. */
		if (dot && fe_encoding_extensions)
		  {
			int i = 0;
			while (fe_encoding_extensions [i])
			  {
				if (!PL_strcmp (dot, fe_encoding_extensions [i]))
				  {
					*dot = 0;
					dot--;
					while (dot > path && *dot != '.')
					  dot--;
					if (*dot != '.')
					  dot = 0;
					break;
				  }
				i++;
			  }
		  }
#endif /* XP_UNIX */

		tmp_filename = WH_TempName(xpTemporary, "MO");
		if (!tmp_filename) {
			PR_FREEIF(stream);
			PR_FREEIF(obj);
			return NULL;
		}
		if (dot)
		  {
			  char * p_tmp;

			  StrAllocCopy(obj->filename, tmp_filename);

			  /* restrict to ascii alphanumeric chars
			   *
			   * this fixes really bad security hole
			   */
			  for(p_tmp = dot+1; *p_tmp != '\0'; p_tmp++)
				  if( (*p_tmp >= '0' && *p_tmp <= '9')
					  || (*p_tmp >= 'A' && *p_tmp <= 'Z')
					  || (*p_tmp >= 'a' && *p_tmp <= 'z')
					  || (*p_tmp == '_')
					  || (*p_tmp == '+')
					  || (*p_tmp == '-'))
				  {
					  /* this is a good character.  Allow it.
					   */
				  }
				  else
				  {
					  *p_tmp = '\0';
					  break;
				  }
				  
			  StrAllocCat(obj->filename, dot);
		  }
		else
		  {
			  StrAllocCopy(obj->filename, tmp_filename);
		  }
		
        PR_Free(path);
		PR_Free(tmp_filename);

		obj->fp = NET_XP_FileOpen(obj->filename, xpTemporary, XP_FILE_WRITE);
			
		TRACEMSG(("Trying to open output file: %s\n", obj->filename));
    
        if(!obj->fp)
		  {
			char *s = NET_ExplainErrorDetails (MK_UNABLE_TO_OPEN_TMP_FILE,
											   obj->filename);
			if (s)
			  {
				FE_Alert (window_id, s);
				PR_Free (s);
			  }
	        return(NULL);
		  }

		/* construct the command like this
		 *
		 * (( COMMAND ); rm %s )&
		 */
        StrAllocCopy(obj->command, "((");

		/* this is a stream writable program that the user wants
		 * to use non streaming
		 */
		if(view_struct->stream_block_size)
			StrAllocCat(obj->command, "cat %s | ");

        StrAllocCat(obj->command, view_struct->system_command);

		PR_snprintf(small_buf, sizeof(small_buf), "); rm %.200s )&", obj->filename);
		StrAllocCat(obj->command, small_buf);
	  }

	StrAllocCopy(obj->url, URL_s->address);
    
    TRACEMSG(("Returning stream from NET_ExtViewer\n"));

    return stream;
}

#endif /* MOZILLA_CLIENT */
