/*		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 "UCDefs.h"
#include "HTAlert.h"
#include "HTFile.h"
#include "HTPlain.h"
#include "HTFile.h"
#ifdef VMS
#include "HTVMSUtils.h"
#endif /* VMS */

#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 */

#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 */

PUBLIC HTStream* HTSaveToFile PARAMS((
        HTPresentation *       pres,
        HTParentAnchor *       anchor,
        HTStream *             sink));

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


/*	Stream Object
**	-------------
*/
struct _HTStream {
	CONST HTStreamClass *	isa;
	
	FILE *			fp;		/* The file we've opened */
	char * 			end_command;	/* What to do on _free.	 */
	char * 			remove_command;	/* What to do on _abort. */
	char * 			viewer_command;	/* Saved external viewer */
	HTFormat		input_format;  /* Original pres->rep     */
	HTFormat		output_format; /* Original pres->rep_out */
	HTParentAnchor *	anchor;	    /* Original stream's anchor. */
	HTStream *		sink;	    /* Original stream's sink.   */
};


/*_________________________________________________________________________
**
**			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)
{
    FILE *fp = NULL;
    int len;
    char *path = NULL;
    char *addr = NULL;
    int status;
    extern int HTLoadFile PARAMS((
    	CONST char *		addr,
	HTParentAnchor *	anchor,
	HTFormat		format_out,
	HTStream *		sink));

    fflush(me->fp);
    if (me->end_command) {		/* Temp file */
    	fclose(me->fp);
#ifdef VMS
	if (0 == strcmp(me->end_command, "SaveVMSBinaryFile")) {
        /*
	 *  It's a binary file saved to disk on VMS, which
	 *  we want to convert to fixed records format. - FM
	 */
#ifdef USE_COMMAND_FILE
	    system(FIXED_RECORD_COMMAND);
#else
	    LYVMS_FixedLengthRecords(FIXED_RECORD_COMMAND);
#endif /* USE_COMMAND_FILE */
	    FREE(FIXED_RECORD_COMMAND);

            if (me->remove_command) {
	        /* NEVER REMOVE THE FILE unless during an abort!!!*/
	        /* system(me->remove_command); */
		FREE(me->remove_command);
	    }
	} else
#endif /* VMS */
	if (me->input_format == HTAtom_for("www/compressed")) {
	    /*
	     *  It's a compressed file supposedly cached to
	     *  a temporary file for uncompression. - FM
	     */
	    if (me->anchor->FileCache != NULL) {
	        /*
		 *  Save the path with the "gz" or "Z" suffix trimmed,
		 *  and remove any previous uncompressed copy. - FM
		 */
	        StrAllocCopy(path, me->anchor->FileCache);
		if ((len = strlen(path)) > 2) {
		    if (!strcasecomp((char *)&path[len-2], "gz")) {
		        path[len-3] = '\0';
		        remove(path);
		    } else if (!strcasecomp((char *)&path[len-1], "Z")) {
		        path[len-2] = '\0';
		        remove(path);
		    }
		}
		if (!dump_output_immediately) {
		    /*
		     *  Tell user what's happening. - FM
		     */
		    _HTProgress(me->end_command);
		}
		/*
		 *  Uncompress it. - FM
		 */
		system(me->end_command);
		if ((fp = fopen(me->anchor->FileCache, "r")) != NULL) {
		    /*
		     *  It's still there with the "gz" of "Z" suffix,
		     *  so the uncompression failed. - FM
		     */
		    fclose(fp);
		    fp = NULL;
		    if (!dump_output_immediately) {
		        clearok(curscr, TRUE);
			refresh();
		    }
		    HTAlert(ERROR_UNCOMPRESSING_TEMP);
		    remove(me->anchor->FileCache);
		    FREE(me->anchor->FileCache);
		} else {
		    /*
		     *  Succeeded!  Save the new path
		     *  in the anchor structure. - FM
		     */
		    StrAllocCopy(me->anchor->FileCache, path);
		    FREE(path);
		    FREE(me->anchor->content_encoding);
		    /*
		     *  If we are not passing the temporary file to a
		     *  helper app, invoke HTLoadFile() to handle it.
		     *  Otherwise, invoke the helper app. - FM & KW
		     */
		    if (me->viewer_command == NULL) {
			/*
			 *  Lock the chartrans info we may possibly have,
			 *  so HTCharsetFormat() will not apply the default
			 *  for local files. - KW
			 */
			if (HTAnchor_getUCLYhndl(me->anchor,
						 UCT_STAGE_PARSER) < 0 ) {
			    /*
			     *  If not yet set - KW
			     */
			    HTAnchor_copyUCInfoStage(me->anchor,
						     UCT_STAGE_PARSER,
						     UCT_STAGE_MIME,
						     UCT_SETBY_DEFAULT+1);
			}
			HTAnchor_copyUCInfoStage(me->anchor,
						 UCT_STAGE_PARSER,
						 UCT_STAGE_MIME, -1);
			/*
			 *  Create a complete address for
			 *  the uncompressed file. - FM
			 */
			StrAllocCopy(addr, "file://localhost");
#ifdef VMS
			StrAllocCat(addr,
				    HTVMS_wwwName(me->anchor->FileCache));
#else
			StrAllocCat(addr, me->anchor->FileCache);
#endif /* VMS */
		     
			if (!dump_output_immediately) {
			    /*
			     *  Tell user what's happening. - FM
			     */
			    _user_message(WWW_USING_MESSAGE, addr);
			}
			/*
			 *  Now have HTLoadFile() handle the uncompressed
			 *  file as if it were the original reply. - FM
			 */
			status = HTLoadFile(addr,
					    me->anchor,
					    me->output_format,
					    me->sink);
			FREE(addr);
		    } else {
		        /*
			 *  Invoke the helper app. - KW
			 */
			FREE(me->end_command);
			me->end_command = (char *)calloc(1,
				((strlen(me->viewer_command) + 10 +
				  strlen(me->anchor->FileCache)) *
				 sizeof(char)));
			if (me->end_command == NULL) {
			    outofmem(__FILE__,
				     "HTFWriter_free(HTCompressed)");
			}
			sprintf(me->end_command,
				me->viewer_command, me->anchor->FileCache,
				"", "", "", "", "", "");
			if (!dump_output_immediately) {
			    /*
			     *  Tell user what's happening. - FM
			     */
			    HTProgress(me->end_command);
			    fflush(stdout);
			    fflush(stderr);
			    stop_curses();
			}
			system(me->end_command);
			fflush(stdout);
			fflush(stderr);
			if (!dump_output_immediately) {
			    start_curses();
			}
			if (me->remove_command) {
			    /* NEVER REMOVE THE FILE unless during an abort!*/
			    /* system(me->remove_command); */
			    FREE(me->remove_command);
			}
		    }
		    if (dump_output_immediately &&
		        me->output_format == HTAtom_for("www/present")) {
			remove(me->anchor->FileCache);
			FREE(me->anchor->FileCache);
			FREE(me->remove_command);
			FREE(me->end_command);
			FREE(me->viewer_command);
			FREE(me);
		        return;
		    }
		}
	    }
            if (me->remove_command) {
	        /* NEVER REMOVE THE FILE unless during an abort!!!*/
	        /* system(me->remove_command); */
		FREE(me->remove_command);
	    }
	} else if (strcmp(me->end_command, "SaveToFile")) {
	    /*
	     *  It's a temporary file we're passing to a
	     *  viewer or helper application. - FM
	     */
	    if (!dump_output_immediately) {
	        /*
		 *  Tell user what's happening. - FM
		 */
                _HTProgress(me->end_command);
	        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();
	} else {
	    /*
	     *  It's a file we saved to disk for handling
	     *  via a menu. - FM
	     */
            if (me->remove_command) {
	        /* NEVER REMOVE THE FILE unless during an abort!!!*/
	        /* system(me->remove_command); */
		FREE(me->remove_command);
	    }
	}
	FREE(me->end_command);
    }
    FREE(me->viewer_command);

    if (dump_output_immediately) {
        if (me->anchor->FileCache)
            remove(me->anchor->FileCache);
	FREE(me);
        (void) signal(SIGHUP, SIG_DFL);
        (void) signal(SIGTERM, SIG_DFL);
#ifndef VMS
        (void) signal(SIGINT, SIG_DFL);
#endif /* !VMS */
#ifdef SIGTSTP
	if (no_suspend)
	  (void) signal(SIGTSTP,SIG_DFL);
#endif /* SIGTSTP */
        exit(0);
    }

    FREE(me);
    return;
}

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

    fclose(me->fp);
    FREE(me->viewer_command);
    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;
    me->viewer_command = NULL;
    me->anchor = NULL;
    me->sink = 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 /* VMS */

/*	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,
	HTStream *,		sink)
{
    char fnam[256];
    CONST char *suffix;
    char *cp;
    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 (dump_output_immediately) {
	    LYCancelledFetch = TRUE;
	    return(NULL);
	}
        if (no_exec) {
            _statusline(EXECUTION_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_DISABLED_FOR_FILE,
			     key_for_func(LYK_OPTIONS));
		_statusline(buf);
		sleep(AlertSecs);
		return HTPlainPresent(pres, anchor, sink);
	   }
    }
#endif /* EXEC_LINKS || EXEC_SCRIPTS */
    
    if (dump_output_immediately) {
        return(HTSaveToFile(pres, anchor, sink));
    }

    me = (HTStream*)calloc(sizeof(*me),1);
    if (me == NULL)
        outofmem(__FILE__, "HTSaveAndExecute");
    me->isa = &HTFWriter;
    me->input_format = pres->rep;
    me->output_format = pres->rep_out;
    me->anchor = anchor;
    me->sink = sink;
    
    if (anchor->FileCache) {
        strcpy(fnam, anchor->FileCache);
	FREE(anchor->FileCache);
        if ((fp = fopen(fnam, "r")) != NULL) {
	    fclose(fp);
	    fp = NULL;
	    remove(fnam);
	}
    } else {
        /*
	 *  Lynx routine to create a temporary filename
	 */
SaveAndExecute_tempname:
        tempname (fnam, NEW_FILE);
	/*
	 *  Check for a suffix.
	 */
	if (((cp = strrchr(fnam, '.')) != NULL) &&
#ifdef VMS
	    NULL == strchr(cp, ']') &&
#endif /* VMS */
	    NULL == strchr(cp, '/')) {
	    /*
	     *  Save the file under a suitably suffixed name.
	     */
	    *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);
		/*
		 *  It's not one of the suffixes checked for a
		 *  spoof in tempname(), so check it now. - FM
		 */
		if ((fp = fopen(fnam, "r")) != NULL) {
		    fclose(fp);
		    fp = NULL;
		    goto SaveAndExecute_tempname;
		}
	    } else {
	        *cp = '.';
	    }
	}
    }
    
#ifdef VMS
    me->fp = fopen (fnam, "wb", "mbc=32");
#else
    me->fp = fopen (fnam, "wb");
#endif /* VMS */

    if (!me->fp) {
	HTAlert(CANNOT_OPEN_TEMP);
	FREE(me);
	return NULL;
    }
    chmod(fnam, 0600);

    StrAllocCopy(me->viewer_command, pres->command);
    /*
     *  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,
        HTStream *,             sink)
{
    HTStream * ret_obj;
    char fnam[256];
    CONST char * suffix;
    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;
    ret_obj->input_format = pres->rep;
    ret_obj->output_format = pres->rep_out;
    ret_obj->anchor = anchor;
    ret_obj->sink = sink;

    if (dump_output_immediately) {
        ret_obj->fp = stdout; /* stdout*/
	if (HTOutputFormat == HTAtom_for("www/download"))
	    goto Prepend_BASE;
        return ret_obj;
    }

    LYCancelDownload = FALSE;
    if (HTOutputFormat != HTAtom_for("www/download")) {
	if (traversal ||
	    (no_download && !override_no_download && no_disk_save)) {
	    if (!traversal) {
                _statusline(CANNOT_DISPLAY_FILE);
	        sleep(AlertSecs);
	    }
	    LYCancelDownload = TRUE;
	    if (traversal)
	        LYCancelledFetch = TRUE;
            FREE(ret_obj);
            return(NULL);
	}

	if (((cp=strchr((char *)pres->rep->name, ';')) != NULL) &&
	    strstr((cp+1), "charset") != NULL) {
	    _user_message(WRONG_CHARSET_D_OR_C, (char *)pres->rep->name);
	} else if (*((char *)pres->rep->name) != '\0')  {
            _user_message(UNMAPPED_TYPE_D_OR_C, (char *)pres->rep->name);
	} else {
            _statusline(CANNOT_DISPLAY_FILE_D_OR_C);
	}

        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 "really exit" query. - FM
 	     */
	    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(anchor->FileCache);
        if ((fp = fopen(fnam, "r")) != NULL) {
	    fclose(fp);
	    fp = NULL;
	    remove(fnam);
	}
    } else {
        /*
	 *  Lynx routine to create a temporary filename
	 */
SaveToFile_tempname:
        tempname(fnam, NEW_FILE);
	/*
	 *  Check for a suffix.
	 */
	if (((cp=strrchr(fnam, '.')) != NULL) &&
#ifdef VMS
	    NULL == strchr(cp, ']') &&
#endif /* VMS */
	    NULL == strchr(cp, '/')) {
	    /*
	     *  Save the file under a suitably suffixed name.
	     */
	    *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);
		/*
		 *  It's not one of the suffixes checked for a
		 *  spoof in tempname(), so check it now. - FM
		 */
		if ((fp = fopen(fnam, "r")) != NULL) {
		    fclose(fp);
		    fp = NULL;
		    goto SaveToFile_tempname;
		}
	    } else {
	        *cp = '.';
	    }
	}
    }

    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(CANNOT_OPEN_OUTPUT);
        FREE(ret_obj);
        return NULL;
    }
    chmod(fnam, 0600);

    /*
     *  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);

    StrAllocCopy(anchor->FileCache, fnam);
Prepend_BASE:
    if (LYPrependBaseToSource &&
        !strncasecomp(pres->rep->name, "text/html", 9)) {
        /*
	 *  Add the document's base as a BASE tag at the top of the file,
	 *  so that any partial or relative URLs within it will be resolved
	 *  relative to that if no BASE tag is present and replaces it.
	 *  Note that the markup will be technically invalid if a DOCTYPE
	 *  declaration, or HTML or HEAD tags, are present, and thus the
	 *  file may need editing for perfection. - FM
	 */
	char *temp = NULL;

	if (anchor->content_base && *anchor->content_base) {
	    StrAllocCopy(temp, anchor->content_base);
	} else if (anchor->content_location && *anchor->content_location) {
	    StrAllocCopy(temp, anchor->content_location);
	}
	if (temp) {
	    collapse_spaces(temp);
	    if (!is_url(temp)) {
	        FREE(temp);
	    }
	}

        fprintf(ret_obj->fp,
		"<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
		anchor->address, (temp ? temp : anchor->address));
	FREE(temp);
    }
    return ret_obj;
}

/*	Set up stream for uncompressing - FM
**	-------------------------------
*/ 
PUBLIC HTStream* HTCompressed ARGS3(
        HTPresentation *,       pres,
        HTParentAnchor *,       anchor,
        HTStream *,             sink)
{
    HTStream* me;
    HTFormat format;
    char *type = NULL;
    HTPresentation *Pres;
    int n, i;
    BOOL can_present = FALSE;
    char fnam[256];
    CONST char *suffix;
    char *uncompress_mask = NULL;
    char *compress_suffix;
    char *cp;
    FILE *fp = NULL;

    /*
     *  Deal with any inappropriate invokations of this function,
     *  or a download request, in which case we won't bother to
     *  uncompress the file. - FM
     */
    if (!(anchor && anchor->content_encoding && anchor->content_type)) {
        /*
	 *  We have no idea what we're dealing with,
	 *  so treat it as a binary stream. - FM
	 */
	format = HTAtom_for("application/octet-stream");
	me = HTStreamStack(format, pres->rep_out, sink, anchor);
	return me;
    }
    n = HTList_count(HTPresentations);
    for (i = 0; i < n; i++) {
        Pres = (HTPresentation *)HTList_objectAt(HTPresentations, i);
	if (!strcasecomp(Pres->rep->name, anchor->content_type) &&
	    Pres->rep_out == WWW_PRESENT) {
	    /*
	     *  We have a presentation mapping for it. - FM
	     */
	    can_present = TRUE;
	    if (!strcasecomp(anchor->content_encoding, "x-gzip") ||
	        !strcasecomp(anchor->content_encoding, "gzip")) {
		/*
		 *  It's compressed with the ugly gzip. - MS
		 */
#ifdef GZIP_PATH
		StrAllocCopy(uncompress_mask, GZIP_PATH);
		StrAllocCat(uncompress_mask, " -d %s");
#else
		/* for sites that can't install gzip for political reasons */
		StrAllocCopy(uncompress_mask, LYNXGUNZIP_PATH);
		StrAllocCat(uncompress_mask, " %s");
#endif
		compress_suffix = "gz";
	    } else if (!strcasecomp(anchor->content_encoding, "x-compress") ||
	    	       !strcasecomp(anchor->content_encoding, "compress")) {
		/*
		 *  It's compressed the good traditional UNIX way. - MS
		 */
		StrAllocCopy(uncompress_mask, UNCOMPRESS_PATH);
		StrAllocCat(uncompress_mask, " %s");
		compress_suffix = "Z";
	    }
	    break;
	}
    }
    if (can_present == FALSE ||			 /* no presentation mapping */
	uncompress_mask == NULL ||		    /* not gzip or compress */
        strchr(anchor->content_type, ';') ||		   /* wrong charset */
	HTOutputFormat == HTAtom_for("www/download") ||		/* download */
	!strcasecomp(pres->rep_out->name, "www/download") ||	/* download */
	(traversal &&	   /* only handle html or plain text for traversals */
	 strcasecomp(anchor->content_type, "text/html") &&
	 strcasecomp(anchor->content_type, "text/plain"))) {
	/*
	 *  Cast the Content-Encoding to a Content-Type
	 *  and pass it back to be handled as that type. - FM
	 */
	if (strchr(anchor->content_encoding, '/') == NULL) {
	    StrAllocCopy(type, "application/");
	    StrAllocCat(type, anchor->content_encoding);
	} else {
	    StrAllocCopy(type, anchor->content_encoding);
	}
	format = HTAtom_for(type);
	FREE(type)
	FREE(uncompress_mask);
	me = HTStreamStack(format, pres->rep_out, sink, anchor);
	return me;
    }

    /*
     *  Set up the stream structure for uncompressing and then
     *  handling based on the uncompressed Content-Type.- FM
     */
    me = (HTStream*)calloc(sizeof(*me),1);
    if (me == NULL)
        outofmem(__FILE__, "HTCompressed");
    me->isa = &HTFWriter;
    me->input_format = pres->rep;
    me->output_format = pres->rep_out;
    me->anchor = anchor;
    me->sink = sink;

    /*
     *  Remove any old versions of the file. - FM
     */
    if (anchor->FileCache) {
        while ((fp = fopen(anchor->FileCache, "r")) != NULL) {
	    fclose(fp);
	    remove(anchor->FileCache);
	}
	FREE(anchor->FileCache);
    }

    /*
     *  Get a new temporary filename and substitute a suitable suffix. - FM
     */
Compressed_tempname:
    tempname(fnam, NEW_FILE);
    if ((cp = strrchr(fnam, '.')) != NULL) {
	*cp = '\0';
	if (!strcasecomp(anchor->content_type, "text/html")) {
#ifdef VMS
	    strcat(fnam, ".html-");
#else
	    strcat(fnam, ".html.");
#endif /* VMS */
	} else if (!strcasecomp(anchor->content_type, "text/plain")) {
#ifdef VMS
	    strcat(fnam, ".txt-");
#else
	    strcat(fnam, ".txt.");
#endif /* VMS */
	} else if (!strcasecomp(anchor->content_type,
				"application/octet-stream")) {
#ifdef VMS
	    strcat(fnam, ".bin-");
#else
	    strcat(fnam, ".bin.");
#endif /* VMS */
	} else if ((suffix =
		    HTFileSuffix(HTAtom_for(anchor->content_type))) &&
	    	   *suffix == '.') {
	    strcat(fnam, suffix);
#ifdef VMS
	    strcat(fnam, "-");
#else
	    strcat(fnam, ".");
#endif /* VMS */
	} else {
	    strcat(fnam, ".");
	}
    } else {
	strcat(fnam, ".");
    }
    strcat(fnam, compress_suffix);
    /*
     *  It's not one of the suffixes checked for a
     *  spoof in tempname(), so check it now. - FM
     */
    if ((fp = fopen(fnam, "r")) != NULL) {
	fclose(fp);
	fp = NULL;
	goto Compressed_tempname;
    }

    /*
     *  Open the file for receiving the compressed input stream. - FM
     */
#ifdef VMS
    me->fp = fopen (fnam, "wb", "mbc=32");
#else
    me->fp = fopen (fnam, "wb");
#endif /* VMS */
    if (!me->fp) {
	HTAlert(CANNOT_OPEN_TEMP);
	FREE(uncompress_mask);
	FREE(me);
	return NULL;
    }
    chmod(fnam, 0600);

    /*
     *  me->viewer_command will be NULL if the converter Pres found above
     *  is not for an external viewer but an internal HTStream converter.
     *  We also don't set it under conditions where HTSaveAndExecute would
     *  disallow execution of the command. - KW
     */
    if (!dump_output_immediately && !traversal
#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
	&& (Pres->quality != 999.0 ||
	    (!no_exec && 	/* allowed exec link or script ? */
	     (local_exec ||
	      (local_exec_on_local_files &&
	       (LYJumpFileURL ||
		!strncmp(anchor->address,"file://localhost",16))))))
#endif /* EXEC_LINKS || EXEC_SCRIPTS */
	) {
	StrAllocCopy(me->viewer_command, Pres->command);
    }

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

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

    /*
     *  Save the filename and return the structure. - FM
     */
    StrAllocCopy(anchor->FileCache, fnam);
    return me;
}

/*      Dump output to stdout - LJM & FM
**      ---------------------
**
*/
PUBLIC HTStream* HTDumpToStdout ARGS3(
        HTPresentation *,       pres,
        HTParentAnchor *,       anchor,
        HTStream *,             sink)
{
    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->anchor = anchor;

    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 */
