/*		FILE WRITER				HTFWrite.h
**		===========
**
**	This version of the stream object just writes to a C file.
**	The file is assumed open and left open.
**
**	Bugs:
**		strings written must be less than buffer size.
*/

#include "HTUtils.h"
#include "tcp.h"
#include "LYCurses.h"
#include "HTFWriter.h"

#include "HTFormat.h"
#include "HTAlert.h"
#include "HTFile.h"
#include "HTPlain.h"

#include "LYStrings.h"
#include "LYUtils.h"
#include "LYGlobalDefs.h"
#include "LYSignal.h"
#include "LYSystem.h"
#include "GridText.h"
#include "LYexit.h"
#include "LYLeaks.h"
#include "LYKeymap.h"

PUBLIC char * WWW_Download_File=NULL; /* contains the name of the temp file
				      ** which is being downloaded into
				      */
PUBLIC char LYCancelDownload=FALSE;   /* exported to HTFormat.c in libWWW */

extern char dump_output_immediately;  /* if true dump to stdout and quit */

#ifdef VMS
extern BOOLEAN HadVMSInterrupt;      /* flag from cleanup_sig()		*/
PRIVATE char * FIXED_RECORD_COMMAND = NULL;
#ifdef USE_COMMAND_FILE		     /* Keep this as an option. - FM	*/
#define FIXED_RECORD_COMMAND_MASK "@Lynx_Dir:FIXED512 %s"
#else
#define FIXED_RECORD_COMMAND_MASK "%s"
PUBLIC unsigned long LYVMS_FixedLengthRecords PARAMS((char *filename));
#endif /* USE_COMMAND_FILE */
#endif /* VMS */

#define FREE(x) if (x) {free(x); x = NULL;}


/*		Stream Object
**		------------
*/

struct _HTStream {
	CONST HTStreamClass *	isa;
	
	FILE *			fp;
	char * 			end_command;
	char * 			remove_command;
};


/*_________________________________________________________________________
**
**			A C T I O N 	R O U T I N E S
**  Bug:
**	All errors are ignored.
*/

/*	Character handling
**	------------------
*/

PRIVATE void HTFWriter_put_character ARGS2(HTStream *, me, char, c)
{
    putc(c, me->fp);
}



/*	String handling
**	---------------
**
**	Strings must be smaller than this buffer size.
*/
PRIVATE void HTFWriter_put_string ARGS2(HTStream *, me, CONST char*, s)
{
    fputs(s, me->fp);
}


/*	Buffer write.  Buffers can (and should!) be big.
**	------------
*/
PRIVATE void HTFWriter_write ARGS3(HTStream *, me, CONST char*, s, int, l)
{
    fwrite(s, 1, l, me->fp); 
}




/*	Free an HTML object
**	-------------------
**
**	Note that the SGML parsing context is freed, but the created
**	object is not,
**	as it takes on an existence of its own unless explicitly freed.
*/

PRIVATE void HTFWriter_free ARGS1(HTStream *, me)
{

    fflush(me->fp);
    if (me->end_command) {		/* Temp file */
    	fclose(me->fp);
#ifdef VMS
	if (0==strcmp(me->end_command, "SaveVMSBinaryFile")) {
#ifdef USE_COMMAND_FILE
	    system(FIXED_RECORD_COMMAND);
#else
	    LYVMS_FixedLengthRecords(FIXED_RECORD_COMMAND);
#endif /* USE_COMMAND_FILE */
	    FREE(FIXED_RECORD_COMMAND);
	} else
#endif /* VMS */
	if (strcmp(me->end_command, "SaveToFile")) {
	    if(!dump_output_immediately) {
                _HTProgress(me->end_command);  /* Tell user what's happening */
	        stop_curses();
	    }
	    system(me->end_command);

            if (me->remove_command) {
	        /* NEVER REMOVE THE FILE unless during an abort!!!*/
	        /* system(me->remove_command); */
		FREE(me->remove_command);
	    }
	    if(!dump_output_immediately)
	        start_curses();
	}
	FREE(me->end_command);
    }

    FREE(me);

    if(dump_output_immediately)
       exit(0);
}

/*	Abort writing
*/

PRIVATE void HTFWriter_abort ARGS2(HTStream *, me, HTError, e)
{
    if(TRACE)
       fprintf(stderr,"HTFWriter_abort called\n");


    fclose(me->fp);
    if (me->end_command) {              /* Temp file */
        if (TRACE) fprintf(stderr,
                "HTFWriter: Aborting: file not executed.\n");
        FREE(me->end_command);
        if (me->remove_command) {
            system(me->remove_command);
            FREE(me->remove_command);
        }
    }

    FREE(WWW_Download_File);

    FREE(me);
}


/*	Structured Object Class
**	-----------------------
*/
PRIVATE CONST HTStreamClass HTFWriter = /* As opposed to print etc */
{		
	"FileWriter",
	HTFWriter_free,
	HTFWriter_abort,
	HTFWriter_put_character, 	HTFWriter_put_string,
	HTFWriter_write
}; 


/*	Subclass-specific Methods
**	-------------------------
*/

PUBLIC HTStream* HTFWriter_new ARGS1(FILE *, fp)
{
    HTStream* me;
    
    if (!fp) return NULL;

    me = (HTStream*)calloc(sizeof(*me),1);
    if (me == NULL) outofmem(__FILE__, "HTFWriter_new");
    me->isa = &HTFWriter;       

    me->fp = fp;
    me->end_command = NULL;
    me->remove_command = NULL;

    return me;
}

/*	Make system command from template
**	---------------------------------
**
**	See mailcap spec for description of template.
*/
/* @@ to be written.  sprintfs will do for now.  */


#ifndef VMS
#define REMOVE_COMMAND "/bin/rm -f %s"
#else
#define REMOVE_COMMAND "delete/noconfirm/nolog %s;"
#endif

/*	Take action using a system command
**	----------------------------------
**
**	originally from Ghostview handling by Marc Andreseen.
**	Creates temporary file, writes to it, executes system command
**	on end-document.  The suffix of the temp file can be given
**	in case the application is fussy, or so that a generic opener can
**	be used.
*/
PUBLIC HTStream* HTSaveAndExecute ARGS3(
	HTPresentation *,	pres,
	HTParentAnchor *,	anchor,	/* Not used */
	HTStream *,		sink)	/* Not used */
{
    char fnam[256];
#ifdef NeXT
    CONST char *suffix;
    char *cp;
#endif /* NeXT */
    HTStream* me;
    FILE *fp = NULL;

    if (traversal) {
        LYCancelledFetch = TRUE;
        return(NULL);
    }

#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
    if(pres->quality == 999.0) { /* exec link */
        if (no_exec) {
            _statusline("Execution is disabled.");
            sleep(AlertSecs);
            return HTPlainPresent(pres, anchor, sink);
        }
	if(!local_exec) 
	   if(local_exec_on_local_files &&
	      (LYJumpFileURL ||
	       !strncmp(anchor->address,"file://localhost",16))) {
		/* allow it to continue */
	   } else {
		char buf[512];

		sprintf(buf,
     "Execution is not enabled for this file. See the Options menu (use %s).",
			key_for_func(LYK_OPTIONS));
		_statusline(buf);
		sleep(AlertSecs);
		return HTPlainPresent(pres, anchor, sink);
	   }
    }
#endif /* EXEC_LINKS || EXEC_SCRIPTS */
    
    me = (HTStream*)calloc(sizeof(*me),1);
    if (me == NULL) outofmem(__FILE__, "HTSaveAndExecute");
    me->isa = &HTFWriter;  
    
    if (anchor->FileCache) {
        strcpy(fnam, anchor->FileCache);
	free_and_clear(&anchor->FileCache);
        if ((fp=fopen(fnam, "r")) != NULL) {
	    fclose(fp);
#ifdef SCO
	    unlink(fnam);
#else
	    remove(fnam);
#endif /* SCO */
	}
    } else {
        tempname (fnam, NEW_FILE);  /* lynx routine to create a filename */
#ifdef NeXT
	/* Save the file under a suitably suffixed name */
	if ((cp=strchr(fnam, '.')) != NULL) {
	    *cp = '\0';
	    if (!strcasecomp(pres->rep->name, "text/html")) {
	        strcat(fnam, ".html");
	    } else if (!strcasecomp(pres->rep->name, "text/plain")) {
	        strcat(fnam, ".txt");
	    } else if (!strcasecomp(pres->rep->name,
	    			    "application/octet-stream")) {
	        strcat(fnam, ".bin");
	    } else if ((suffix = HTFileSuffix(pres->rep)) && *suffix == '.') {
	        strcat(fnam, suffix);
	    } else {
	        *cp = '.';
	    }
	}
#endif /* NeXT */
    }
    
#ifdef VMS
    me->fp = fopen (fnam, "wb", "mbc=32");
#else
    me->fp = fopen (fnam, "wb");
#endif /* VMS */

    if (!me->fp) {
	HTAlert("Can't open temporary file!");
	FREE(me);
	return NULL;
    }

/*	Make command to process file
*/
    me->end_command = (char *)calloc (
    			(strlen (pres->command) + 10 + strlen(fnam))
    			 * sizeof (char),1);
    if (me->end_command == NULL) outofmem(__FILE__, "HTSaveAndExecute");
    
    sprintf (me->end_command, pres->command, fnam, "");

/*	Make command to delete file
*/ 
    me->remove_command = (char *)calloc (
    			(strlen (REMOVE_COMMAND) + 10 + strlen(fnam))
    			 * sizeof (char),1);
    if (me->remove_command == NULL) outofmem(__FILE__, "HTSaveAndExecute");
    
    sprintf (me->remove_command, REMOVE_COMMAND, fnam);

    StrAllocCopy(anchor->FileCache, fnam);
    return me;
}


/*	Format Converter using system command
**	-------------------------------------
*/

/* @@@@@@@@@@@@@@@@@@@@@@ */

/*      Save to a local file   LJM!!!
**      --------------------
**
**      usually a binary file that can't be displayed
**
**      originally from Ghostview handling by Marc Andreseen.
**      Asks the user if he wants to continue, creates a temporary
**      file, and writes to it.  In HTSaveToFile_Free
**      the user will see a list of choices for download
*/
PUBLIC HTStream* HTSaveToFile ARGS3(
        HTPresentation *,       pres,
        HTParentAnchor *,       anchor, /* Not used */
        HTStream *,             sink)   /* Not used */
{
    HTStream * ret_obj;
    char fnam[256];
#ifdef NeXT
    CONST char * suffix;
#endif /* NeXT */
    char *cp;
    int c=0;
    BOOL IsBinary = TRUE;
    FILE *fp = NULL;

    ret_obj = (HTStream*)calloc(sizeof(* ret_obj),1);
    if (ret_obj == NULL) outofmem(__FILE__, "HTSaveToFile");
    ret_obj->isa = &HTFWriter;
    ret_obj->remove_command = NULL;
    ret_obj->end_command = NULL;

    if(dump_output_immediately) {
        ret_obj->fp = stdout; /* stdout*/
        return ret_obj;
    }

    LYCancelDownload = FALSE;
    if(HTOutputFormat != HTAtom_for("www/download")) {
	if (traversal ||
	    (no_download && !override_no_download && no_disk_save)) {
	    if (!traversal) {
                _statusline("This file cannot be displayed on this terminal.");
	        sleep(AlertSecs);
	    }
	    LYCancelDownload = TRUE;
	    if (traversal)
	        LYCancelledFetch = TRUE;
            FREE(ret_obj);
            return(NULL);
	}

	if (((cp=strchr((char *)pres->rep->name, ';')) != NULL) &&
	    !strncmp((cp+1), "charset=", 8)) {
	    _user_message("%s  D)ownload, or C)ancel", (char *)pres->rep->name);
	} else {
            _statusline(
      "This file cannot be displayed on this terminal:  D)ownload, or C)ancel");
	}

        while(TOUPPER(c)!='C' && TOUPPER(c)!='D' && c!=7) {
	    c=LYgetch();
#ifdef VMS
	    /** 'C'ancel on Control-C or Control-Y and a 'N'o to the exit query
 	     **/
	    if (HadVMSInterrupt) {
	        HadVMSInterrupt = FALSE;
	        c = 'C';
	    }
#endif /* VMS */
        }

        /** Cancel on 'C', 'c' or Control-G or Control-C **/
        if(TOUPPER(c)=='C' || c==7 || c==3) {
            _statusline("Cancelling file.");
	    LYCancelDownload = TRUE;
            FREE(ret_obj);
            return(NULL);
        }
    }

/*	Set up a 'D'ownload
*/
    if (anchor->FileCache) {
        strcpy(fnam, anchor->FileCache);
	free_and_clear(&anchor->FileCache);
        if ((fp=fopen(fnam, "r")) != NULL) {
	    fclose(fp);
#ifdef SCO
	    unlink(fnam);
#else
	    remove(fnam);
#endif /* SCO */
	}
    } else {
        tempname(fnam,NEW_FILE);
#ifdef NeXT
	/* Save the file under a suitably suffixed name */
	if ((cp=strchr(fnam, '.')) != NULL) {
	    *cp = '\0';
	    if (!strcasecomp(pres->rep->name, "text/html")) {
	        strcat(fnam, ".html");
	    } else if (!strcasecomp(pres->rep->name, "text/plain")) {
	        strcat(fnam, ".txt");
	    } else if (!strcasecomp(pres->rep->name,
	    			    "application/octet-stream")) {
	        strcat(fnam, ".bin");
	    } else if ((suffix = HTFileSuffix(pres->rep)) && *suffix == '.') {
	        strcat(fnam, suffix);
	    } else {
	        *cp = '.';
	    }
	}
#endif /* NeXT */
    }

    if(0==strncasecomp(pres->rep->name, "text/", 5) ||
       0==strcasecomp(pres->rep->name, "application/postscript") ||
       0==strcasecomp(pres->rep->name, "application/x-RUNOFF-MANUAL"))
        /*
	 *  It's a text file requested via 'd'ownload.
	 *  Keep adding others to the above list, 'til
	 *  we add a configurable procedure. - FM
	 */
	IsBinary = FALSE;

#ifdef VMS
    ret_obj->fp = fopen (fnam, "wb", "mbc=32");
#else
    ret_obj->fp = fopen (fnam, "wb");
#endif /* VMS */

    if (!ret_obj->fp) {
        HTAlert("Can't open output file! Cancelling");
        FREE(ret_obj);
        return NULL;
    }

    /*
     *  Any "application/foo" or other non-"text/foo" types that
     *  are actually text but not checked, above, will be treated
     *  as binary, so show the type to help sort that out later.
     *  Unix folks don't need to know this, but we'll show it to
     *  them, too. - FM
     */
    user_message("Content-type: %s", pres->rep->name);
    sleep(MessageSecs);

    StrAllocCopy(WWW_Download_File,fnam);

/*	Make command to delete file
*/ 
    ret_obj->remove_command = (char *)calloc (
    			(strlen (REMOVE_COMMAND) + 10+ strlen(fnam))
    			 * sizeof (char),1);
    if (ret_obj->remove_command == NULL) outofmem(__FILE__, "HTSaveToFile");
    
    sprintf (ret_obj->remove_command, REMOVE_COMMAND, fnam);

#ifdef VMS
    if (IsBinary && UseFixedRecords) {
        ret_obj->end_command = (char *)calloc (sizeof(char)*20,1);
        if (ret_obj->end_command == NULL) outofmem(__FILE__, "HTSaveToFile");
        sprintf(ret_obj->end_command, "SaveVMSBinaryFile");
        FIXED_RECORD_COMMAND = (char *)calloc (
    		(strlen (FIXED_RECORD_COMMAND_MASK) + 10 + strlen(fnam))
    		* sizeof (char),1);
        if (FIXED_RECORD_COMMAND == NULL) outofmem(__FILE__, "HTSaveToFile");
        sprintf(FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, fnam);
    } else {
#endif /* VMS */
    ret_obj->end_command = (char *)calloc (sizeof(char)*12,1);
    if (ret_obj->end_command == NULL) outofmem(__FILE__, "HTSaveToFile");
    sprintf(ret_obj->end_command, "SaveToFile");
#ifdef VMS
    }
#endif /* VMS */

    _statusline("Retrieving file.  - PLEASE WAIT -");

    StrAllocCopy(anchor->FileCache, fnam);
    return ret_obj;
}

/*      Dump output to stdout LJM!!!
**      ---------------------
**
*/
PUBLIC HTStream* HTDumpToStdout ARGS3(
        HTPresentation *,       pres,
        HTParentAnchor *,       anchor, /* Not used */
        HTStream *,             sink)   /* Not used */
{
    HTStream * ret_obj;
    ret_obj = (HTStream*)calloc(sizeof(* ret_obj),1);
    if (ret_obj == NULL) outofmem(__FILE__, "HTDumpToStdout");
    ret_obj->isa = &HTFWriter;
    ret_obj->remove_command = NULL;
    ret_obj->end_command = NULL;

    ret_obj->fp = stdout; /* stdout*/
    return ret_obj;
}

#if defined(VMS) && !defined(USE_COMMAND_FILE)
#include <fab.h>
#include <rmsdef.h>             /* RMS status codes */
#include <iodef.h>              /* I/O function codes */
#include <fibdef.h>             /* file information block defs */
#include <atrdef.h>             /* attribute request codes */
#ifdef NOTDEFINED /*** Not all versions of VMS compilers have these.     ***/
#include <fchdef.h>             /* file characteristics */
#include <fatdef.h>             /* file attribute defs */
#else		  /*** So we'll define what we need from them ourselves. ***/
#define FCH$V_CONTIGB	0x005			/* pos of cont best try bit */
#define FCH$M_CONTIGB	(1 << FCH$V_CONTIGB)	/* contig best try bit mask */
/* VMS I/O User's Reference Manual: Part I (V5.x doc set) */
struct fatdef {
    unsigned char       fat$b_rtype,    fat$b_rattrib;
    unsigned short      fat$w_rsize;
    unsigned long       fat$l_hiblk,    fat$l_efblk;
    unsigned short      fat$w_ffbyte;
    unsigned char       fat$b_bktsize,  fat$b_vfcsize;
    unsigned short      fat$w_maxrec,   fat$w_defext,   fat$w_gbc;
    unsigned    : 16, : 32, : 16;   /* 6 bytes reserved, 2 bytes not used */
    unsigned short      fat$w_versions;
};
#endif /* NOTDEFINED */

/* arbitrary descriptor without type and class info */
typedef struct dsc { unsigned short len, mbz; void *adr; } Desc;

extern unsigned long    sys$open(),  sys$qiow(),  sys$dassgn();

#define syswork(sts)    ((sts) & 1)
#define sysfail(sts)    (!syswork(sts))


/*
 *  25-Jul-1995 - Pat Rankin (rankin@eql.caltech.edu)
 *
 *  Force a file to be marked as having fixed-length, 512 byte records
 *  without implied carriage control, and with best_try_contiguous set.
 */
PUBLIC unsigned long LYVMS_FixedLengthRecords ARGS1(char *, filename)
{
    struct FAB      fab;                /* RMS file access block */
    struct fibdef   fib;                /* XQP file information block */
    struct fatdef   recattr;            /* XQP file "record" attributes */
    struct atrdef   attr_rqst_list[3];  /* XQP attribute request itemlist */

    Desc            fib_dsc;
    unsigned short  channel,  iosb[4];
    unsigned long   fchars,  sts,  tmp;

    /* initialize file access block */
    fab = cc$rms_fab;
    fab.fab$l_fna = filename;
    fab.fab$b_fns = (unsigned char) strlen(filename);
    fab.fab$l_fop = FAB$M_UFO;  /* user file open; no further RMS processing */
    fab.fab$b_fac = FAB$M_PUT;  /* need write access */
    fab.fab$b_shr = FAB$M_NIL;  /* exclusive access */

    sts = sys$open(&fab);       /* channel in stv; $dassgn to close */
    if (sts == RMS$_FLK) {
	/* For MultiNet, at least, if the file was just written by a remote
	   NFS client, the local NFS server might still have it open, and the
	   failed access attempt will provoke it to be closed, so try again. */
	sts = sys$open(&fab);
    }
    if (sysfail(sts)) return sts;

    /* RMS supplies a user-mode channel (see FAB$L_FOP FAB$V_UFO doc) */
    channel = (unsigned short) fab.fab$l_stv;

    /* set up ACP interface strutures */
    /* file information block, passed by descriptor; it's okay to start with
       an empty FIB after RMS has accessed the file for us */
    fib_dsc.len = sizeof fib;
    fib_dsc.mbz = 0;
    fib_dsc.adr = &fib;
    memset((void *)&fib, 0, sizeof fib);
    /* attribute request list */
    attr_rqst_list[0].atr$w_size = sizeof recattr;
    attr_rqst_list[0].atr$w_type = ATR$C_RECATTR;
    *(void **)&attr_rqst_list[0].atr$l_addr = (void *)&recattr;
    attr_rqst_list[1].atr$w_size = sizeof fchars;
    attr_rqst_list[1].atr$w_type = ATR$C_UCHAR;
    *(void **)&attr_rqst_list[1].atr$l_addr = (void *)&fchars;
    attr_rqst_list[2].atr$w_size = attr_rqst_list[2].atr$w_type = 0;
    attr_rqst_list[2].atr$l_addr = 0;
    /* file "record" attributes */
    memset((void *)&recattr, 0, sizeof recattr);
    fchars = 0;         /* file characteristics */

    /* get current attributes */
    sts = sys$qiow(0, channel, IO$_ACCESS, iosb, (void(*)())0, 0,
		   &fib_dsc, 0, 0, 0, attr_rqst_list, 0);
    if (syswork(sts))
        sts = iosb[0];

    /* set desired attributes */
    if (syswork(sts)) {
	recattr.fat$b_rtype = FAB$C_SEQ | FAB$C_FIX;    /* org=seq, rfm=fix */
	recattr.fat$w_rsize = recattr.fat$w_maxrec = 512;   /* lrl=mrs=512 */
	recattr.fat$b_rattrib = 0;                      /* rat=none */
	fchars |= FCH$M_CONTIGB;                /* contiguous-best-try */
	sts = sys$qiow(0, channel, IO$_DEACCESS, iosb, (void(*)())0, 0,
		       &fib_dsc, 0, 0, 0, attr_rqst_list, 0);
	if (syswork(sts))
	    sts = iosb[0];
    }

    /* all done */
    tmp = sys$dassgn(channel);
    if (syswork(sts))
        sts = tmp;
    return sts;
}
#endif /* VMS && !USE_COMMAND_FILE */
