summaryrefslogtreecommitdiffstats
path: root/src/HTFWriter.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:37:15 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:37:15 +0000
commitae5d181b854d3ccb373b6bc01b4869e44ff4d87a (patch)
tree91f59efb48c56a84cc798e012fccb667b63d3fee /src/HTFWriter.c
parentInitial commit. (diff)
downloadlynx-ae5d181b854d3ccb373b6bc01b4869e44ff4d87a.tar.xz
lynx-ae5d181b854d3ccb373b6bc01b4869e44ff4d87a.zip
Adding upstream version 2.9.0dev.12.upstream/2.9.0dev.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/HTFWriter.c')
-rw-r--r--src/HTFWriter.c1516
1 files changed, 1516 insertions, 0 deletions
diff --git a/src/HTFWriter.c b/src/HTFWriter.c
new file mode 100644
index 0000000..9b7c0c6
--- /dev/null
+++ b/src/HTFWriter.c
@@ -0,0 +1,1516 @@
+/*
+ * $LynxId: HTFWriter.c,v 1.124 2022/07/25 00:16:38 tom Exp $
+ *
+ * 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.
+ */
+
+#define HTSTREAM_INTERNAL 1
+
+#include <HTUtils.h>
+#include <LYCurses.h>
+#include <HTFWriter.h>
+#include <HTSaveToFile.h>
+
+#ifdef WIN_EX
+#include <HTParse.h>
+#endif
+
+#include <HTFormat.h>
+#include <UCDefs.h>
+#include <HTAlert.h>
+#include <HTFile.h>
+#include <HTInit.h>
+#include <HTPlain.h>
+
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYClean.h>
+#include <GridText.h>
+#include <LYExtern.h>
+#include <LYexit.h>
+#include <LYLeaks.h>
+#include <LYKeymap.h>
+#include <LYGetFile.h>
+#include <LYHistory.h> /* store statusline messages */
+
+#ifdef USE_PERSISTENT_COOKIES
+#include <LYCookie.h>
+#endif
+
+/* contains the name of the temp file which is being downloaded into */
+char *WWW_Download_File = NULL;
+BOOLEAN LYCancelDownload = FALSE; /* exported to HTFormat.c in libWWW */
+
+#ifdef VMS
+static 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"
+static unsigned long LYVMS_FixedLengthRecords(char *filename);
+#endif /* USE_COMMAND_FILE */
+#endif /* VMS */
+
+HTStream *HTSaveToFile(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+/* 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. */
+#ifdef FNAMES_8_3
+ BOOLEAN idash; /* remember position to become '.' */
+#endif
+};
+
+/*_________________________________________________________________________
+ *
+ * A C T I O N R O U T I N E S
+ * Bug:
+ * Most errors are ignored.
+ */
+
+/* Error handling
+ * ------------------
+ */
+static void HTFWriter_error(HTStream *me, const char *id)
+{
+ char buf[200];
+
+ sprintf(buf, "%.60s: %.60s: %.60s",
+ id,
+ me->isa->name,
+ LYStrerror(errno));
+ HTAlert(buf);
+/*
+ * Only disaster results from:
+ * me->isa->_abort(me, NULL);
+ */
+}
+
+/* Character handling
+ * ------------------
+ */
+static void HTFWriter_put_character(HTStream *me, int c)
+{
+ if (me->fp) {
+ putc(c, me->fp);
+ }
+}
+
+/* String handling
+ * ---------------
+ */
+static void HTFWriter_put_string(HTStream *me, const char *s)
+{
+ if (me->fp) {
+ fputs(s, me->fp);
+ }
+}
+
+/* Buffer write. Buffers can (and should!) be big.
+ * ------------
+ */
+static void HTFWriter_write(HTStream *me, const char *s, int l)
+{
+ size_t result;
+
+ if (me->fp) {
+ result = fwrite(s, (size_t) 1, (size_t) l, me->fp);
+ if (result != (size_t) l) {
+ HTFWriter_error(me, "HTFWriter_write");
+ }
+ }
+}
+
+static void decompress_gzip(HTStream *me)
+{
+ char *in_name = me->anchor->FileCache;
+ char copied[LY_MAXPATH];
+ FILE *fp = LYOpenTemp(copied, ".tmp.gz", BIN_W);
+
+ if (fp != 0) {
+#ifdef USE_ZLIB
+ char buffer[BUFSIZ];
+ gzFile gzfp;
+ int status;
+
+ CTRACE((tfp, "decompressing '%s'\n", in_name));
+ if ((gzfp = gzopen(in_name, BIN_R)) != 0) {
+ BOOL success = TRUE;
+ size_t actual = 0;
+
+ CTRACE((tfp, "...opened '%s'\n", copied));
+ while ((status = gzread(gzfp, buffer, sizeof(buffer))) > 0) {
+ size_t want = (size_t) status;
+ size_t have = fwrite(buffer, sizeof(char), want, fp);
+
+ actual += have;
+ if (want != have) {
+ success = FALSE;
+ break;
+ }
+ }
+ gzclose(gzfp);
+ LYCloseTempFP(fp);
+ CTRACE((tfp, "...decompress %" PRI_off_t " to %lu\n",
+ CAST_off_t (me->anchor->actual_length),
+ (unsigned long)actual));
+ if (success) {
+ if (LYRenameFile(copied, in_name) == 0)
+ me->anchor->actual_length = (off_t) actual;
+ (void) LYRemoveTemp(copied);
+ }
+ }
+#else
+#define FMT "%s %s"
+ const char *program;
+
+ if (LYCopyFile(in_name, copied) == 0) {
+ char expanded[LY_MAXPATH];
+ char *command = NULL;
+
+ if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) {
+ HTAddParam(&command, FMT, 1, program);
+ HTAddParam(&command, FMT, 2, copied);
+ HTEndParam(&command, FMT, 2);
+ }
+ if (LYSystem(command) == 0) {
+ struct stat stat_buf;
+
+ strcpy(expanded, copied);
+ *strrchr(expanded, '.') = '\0';
+ if (LYRenameFile(expanded, in_name) != 0) {
+ CTRACE((tfp, "rename failed %s to %s\n", expanded, in_name));
+ } else if (stat(in_name, &stat_buf) != 0) {
+ CTRACE((tfp, "stat failed for %s\n", in_name));
+ } else {
+ me->anchor->actual_length = stat_buf.st_size;
+ }
+ } else {
+ CTRACE((tfp, "command failed: %s\n", command));
+ }
+ free(command);
+ (void) LYRemoveTemp(copied);
+ }
+#undef FMT
+#endif
+ }
+}
+
+/* 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.
+ */
+static void HTFWriter_free(HTStream *me)
+{
+ int len;
+ char *path = NULL;
+ char *addr = NULL;
+ BOOL use_zread = NO;
+ BOOLEAN found = FALSE;
+
+#ifdef WIN_EX
+ HANDLE cur_handle;
+
+ cur_handle = GetForegroundWindow();
+#endif
+
+ if (me->fp)
+ fflush(me->fp);
+ if (me->end_command) { /* Temp file */
+ LYCloseTempFP(me->fp);
+ /*
+ * Handle a special case where the server used "Content-Type: gzip".
+ * Normally that feeds into the presentation stages, but if the link
+ * happens to point to something that will not be presented, but
+ * instead offered as a download, it comes here. In that case, ungzip
+ * the content before prompting the user for the place to store it.
+ */
+ if (me->anchor->FileCache != NULL
+ && me->anchor->no_content_encoding == FALSE
+ && me->input_format == HTAtom_for("application/x-gzip")
+ && !strcmp(me->anchor->content_encoding, "gzip")) {
+ decompress_gzip(me);
+ }
+#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
+ LYSystem(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! */
+ 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) {
+ BOOL skip_loadfile = (BOOL) (me->viewer_command != 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 = (int) strlen(path)) > 3 &&
+ (!strcasecomp(&path[len - 2], "gz") ||
+ !strcasecomp(&path[len - 2], "zz"))) {
+#ifdef USE_ZLIB
+ if (!skip_loadfile) {
+ use_zread = YES;
+ } else
+#endif /* USE_ZLIB */
+ {
+ path[len - 3] = '\0';
+ (void) remove(path);
+ }
+ } else if (len > 4 && !strcasecomp(&path[len - 3], "bz2")) {
+#ifdef USE_BZLIB
+ if (!skip_loadfile) {
+ use_zread = YES;
+ } else
+#endif /* USE_BZLIB */
+ {
+ path[len - 4] = '\0';
+ (void) remove(path);
+ }
+ } else if (len > 3 && !strcasecomp(&path[len - 2], "br")) {
+#ifdef USE_BROTLI
+ if (!skip_loadfile) {
+ use_zread = YES;
+ } else
+#endif /* USE_BROTLI */
+ {
+ path[len - 3] = '\0';
+ (void) remove(path);
+ }
+ } else if (len > 2 && !strcasecomp(&path[len - 1], "Z")) {
+ path[len - 2] = '\0';
+ (void) remove(path);
+ }
+ if (!use_zread) {
+ if (!dump_output_immediately) {
+ /*
+ * Tell user what's happening. - FM
+ */
+ _HTProgress(me->end_command);
+ }
+ /*
+ * Uncompress it. - FM
+ */
+ if (!isEmpty(me->end_command))
+ LYSystem(me->end_command);
+ found = LYCanReadFile(me->anchor->FileCache);
+ }
+ if (found) {
+ /*
+ * It's still there with the "gz" or "Z" suffix,
+ * so the uncompression failed. - FM
+ */
+ if (!dump_output_immediately) {
+ lynx_force_repaint();
+ LYrefresh();
+ }
+ HTAlert(ERROR_UNCOMPRESSING_TEMP);
+ (void) LYRemoveTemp(me->anchor->FileCache);
+ FREE(me->anchor->FileCache);
+ } else {
+ /*
+ * Succeeded! Create a complete address
+ * for the uncompressed file and invoke
+ * HTLoadFile() to handle it. - FM
+ */
+#ifdef FNAMES_8_3
+ /*
+ * Assuming we have just uncompressed e.g.
+ * FILE-mpeg.gz -> FILE-mpeg, restore/shorten
+ * the name to be fit for passing to an external
+ * viewer, by renaming FILE-mpeg -> FILE.mpe - kw
+ */
+ if (skip_loadfile) {
+ char *new_path = NULL;
+ char *the_dash = me->idash ? strrchr(path, '-') : 0;
+
+ if (the_dash != 0) {
+ unsigned off = (the_dash - path);
+
+ StrAllocCopy(new_path, path);
+ new_path[off] = '.';
+ if (strlen(new_path + off) > 4)
+ new_path[off + 4] = '\0';
+ if (LYRenameFile(path, new_path) == 0) {
+ FREE(path);
+ path = new_path;
+ } else {
+ FREE(new_path);
+ }
+ }
+ }
+#endif /* FNAMES_8_3 */
+ LYLocalFileToURL(&addr, path);
+ if (!use_zread) {
+ LYRenamedTemp(me->anchor->FileCache, path);
+ StrAllocCopy(me->anchor->FileCache, path);
+ StrAllocCopy(me->anchor->content_encoding, "binary");
+ }
+ FREE(path);
+ if (!skip_loadfile) {
+ /*
+ * 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
+ */
+ if (!dump_output_immediately) {
+ /*
+ * Tell user what's happening. - FM
+ * HTInfoMsg2(WWW_USING_MESSAGE, addr);
+ * but only in the history, not on screen -RS
+ */
+ LYstore_message2(WWW_USING_MESSAGE, addr);
+ }
+
+ if (skip_loadfile) {
+ /*
+ * It's a temporary file we're passing to a viewer or
+ * helper application. Loading the temp file through
+ * HTLoadFile() would result in yet another HTStream
+ * (created with HTSaveAndExecute()) which would just
+ * copy the temp file to another temp file (or even the
+ * same!). We can skip this needless duplication by
+ * using the viewer_command which has already been
+ * determined when the HTCompressed stream was created.
+ * - kw
+ */
+ FREE(me->end_command);
+
+ HTAddParam(&(me->end_command), me->viewer_command, 1, me->anchor->FileCache);
+ HTEndParam(&(me->end_command), me->viewer_command, 1);
+
+ if (!dump_output_immediately) {
+ /*
+ * Tell user what's happening. - FM
+ */
+ HTProgress(me->end_command);
+#ifndef WIN_EX
+ stop_curses();
+#endif
+ }
+#ifdef _WIN_CC
+ exec_command(me->end_command, FALSE);
+#else
+ LYSystem(me->end_command);
+#endif
+ if (me->remove_command) {
+ /* NEVER REMOVE THE FILE unless during an abort!!! */
+ FREE(me->remove_command);
+ }
+ if (!dump_output_immediately) {
+#ifdef WIN_EX
+ if (focus_window) {
+ HTInfoMsg(gettext("Set focus1"));
+ (void) SetForegroundWindow(cur_handle);
+ }
+#else
+ start_curses();
+#endif
+ }
+ } else {
+ (void) HTLoadFile(addr,
+ me->anchor,
+ me->output_format,
+ me->sink);
+ }
+ if (dump_output_immediately &&
+ me->output_format == WWW_PRESENT) {
+ FREE(addr);
+ (void) remove(me->anchor->FileCache);
+ FREE(me->anchor->FileCache);
+ FREE(me->remove_command);
+ FREE(me->end_command);
+ FREE(me->viewer_command);
+ FREE(me);
+ return;
+ }
+ }
+ FREE(addr);
+ }
+ if (me->remove_command) {
+ /* NEVER REMOVE THE FILE unless during an abort!!! */
+ 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);
+#ifndef WIN_EX
+ stop_curses();
+#endif
+ }
+#ifdef _WIN_CC
+ exec_command(me->end_command, wait_viewer_termination);
+#else
+ LYSystem(me->end_command);
+#endif
+
+ if (me->remove_command) {
+ /* NEVER REMOVE THE FILE unless during an abort!!! */
+ FREE(me->remove_command);
+ }
+ if (!dump_output_immediately) {
+#ifdef WIN_EX
+ if (focus_window) {
+ HTInfoMsg(gettext("Set focus2"));
+ (void) SetForegroundWindow(cur_handle);
+ }
+#else
+ start_curses();
+#endif
+ }
+ } 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!!! */
+ FREE(me->remove_command);
+ }
+ if (!dump_output_immediately) {
+#ifdef WIN_EX
+ if (focus_window) {
+ HTInfoMsg(gettext("Set focus3"));
+ (void) SetForegroundWindow(cur_handle);
+ }
+#else
+ start_curses();
+#endif
+ }
+ }
+ FREE(me->end_command);
+ }
+ FREE(me->viewer_command);
+
+ if (dump_output_immediately) {
+ if (me->anchor->FileCache)
+ (void) remove(me->anchor->FileCache);
+ FREE(me);
+#ifdef USE_PERSISTENT_COOKIES
+ /*
+ * We want to save cookies picked up when in source mode. ...
+ */
+ if (persistent_cookies)
+ LYStoreCookies(LYCookieSaveFile);
+#endif /* USE_PERSISTENT_COOKIES */
+ exit_immediately(EXIT_SUCCESS);
+ }
+
+ FREE(me);
+ return;
+}
+
+#ifdef VMS
+# define REMOVE_COMMAND "delete/noconfirm/nolog %s;"
+#else
+# define REMOVE_COMMAND "%s"
+#endif /* VMS */
+
+/* Abort writing
+ * -------------
+ */
+static void HTFWriter_abort(HTStream *me, HTError e GCC_UNUSED)
+{
+ CTRACE((tfp, "HTFWriter_abort called\n"));
+ LYCloseTempFP(me->fp);
+ FREE(me->viewer_command);
+ if (me->end_command) { /* Temp file */
+ CTRACE((tfp, "HTFWriter: Aborting: file not executed or saved.\n"));
+ FREE(me->end_command);
+ if (me->remove_command) {
+#ifdef VMS
+ LYSystem(me->remove_command);
+#else
+ (void) chmod(me->remove_command, 0600); /* Ignore errors */
+ if (0 != unlink(me->remove_command)) {
+ char buf[560];
+
+ sprintf(buf, "%.60s '%.400s': %.60s",
+ gettext("Error deleting file"),
+ me->remove_command, LYStrerror(errno));
+ HTAlert(buf);
+ }
+#endif
+ FREE(me->remove_command);
+ }
+ }
+
+ FREE(WWW_Download_File);
+
+ FREE(me);
+}
+
+/* Structured Object Class
+ * -----------------------
+ */
+static const HTStreamClass HTFWriter = /* As opposed to print etc */
+{
+ "FileWriter",
+ HTFWriter_free,
+ HTFWriter_abort,
+ HTFWriter_put_character,
+ HTFWriter_put_string,
+ HTFWriter_write
+};
+
+/* Subclass-specific Methods
+ * -------------------------
+ */
+HTStream *HTFWriter_new(FILE *fp)
+{
+ HTStream *me;
+
+ if (!fp)
+ return NULL;
+
+ me = typecalloc(HTStream);
+ if (me == NULL)
+ outofmem(__FILE__, "HTFWriter_new");
+
+ me->isa = &HTFWriter;
+
+ me->fp = fp;
+ me->end_command = NULL;
+ me->remove_command = NULL;
+ me->anchor = NULL;
+ me->sink = NULL;
+
+ return me;
+}
+
+/* Make system command from template
+ * ---------------------------------
+ *
+ * See mailcap spec for description of template.
+ */
+static char *mailcap_substitute(HTParentAnchor *anchor,
+ HTPresentation *pres,
+ char *fnam)
+{
+ char *result = LYMakeMailcapCommand(pres->command,
+ anchor->content_type_params,
+ fnam);
+
+#if defined(UNIX)
+ /* if we don't have a "%s" token, expect to provide the file via stdin */
+ if (!LYMailcapUsesPctS(pres->command)) {
+ char *prepend = 0;
+ const char *format = "( %s ) < %s";
+
+ HTSprintf(&prepend, "( %s", result); /* ...avoid quoting */
+ HTAddParam(&prepend, format, 2, fnam); /* ...to quote if needed */
+ FREE(result);
+ result = prepend;
+ }
+#endif
+ return result;
+}
+
+/* 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.
+ */
+HTStream *HTSaveAndExecute(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ char fnam[LY_MAXPATH];
+ const char *suffix;
+ HTStream *me;
+
+ 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) {
+ HTAlert(EXECUTION_DISABLED);
+ 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 = 0;
+
+ HTSprintf0(&buf, EXECUTION_DISABLED_FOR_FILE,
+ key_for_func(LYK_OPTIONS));
+ HTAlert(buf);
+ FREE(buf);
+ return HTPlainPresent(pres, anchor, sink);
+ }
+ }
+ }
+#endif /* EXEC_LINKS || EXEC_SCRIPTS */
+
+ if (dump_output_immediately) {
+ return (HTSaveToFile(pres, anchor, sink));
+ }
+
+ me = typecalloc(HTStream);
+ 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 (LYCachedTemp(fnam, &(anchor->FileCache))) {
+ /* This used to be LYNewBinFile(fnam); changed to a different call so
+ * that the open fp gets registered in the list keeping track of temp
+ * files, equivalent to when LYOpenTemp() gets called below. This
+ * avoids a file descriptor leak caused by LYCloseTempFP() not being
+ * able to find the fp. The binary suffix is expected to not be used,
+ * it's only for fallback in unusual error cases. - kw
+ */
+ me->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W);
+ } else {
+#if defined(WIN_EX) && !defined(__CYGWIN__) /* 1998/01/04 (Sun) */
+ if (!StrNCmp(anchor->address, "file://localhost", 16)) {
+
+ /* 1998/01/23 (Fri) 17:38:26 */
+ char *cp, *view_fname;
+
+ me->fp = NULL;
+
+ view_fname = fnam + 3;
+ LYStrNCpy(view_fname, anchor->address + 17, sizeof(fnam) - 5);
+ HTUnEscape(view_fname);
+
+ if (StrChr(view_fname, ':') == NULL) {
+ fnam[0] = windows_drive[0];
+ fnam[1] = windows_drive[1];
+ fnam[2] = '/';
+ view_fname = fnam;
+ }
+
+ /* 1998/04/21 (Tue) 11:04:16 */
+ cp = view_fname;
+ while (*cp) {
+ if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp))) {
+ cp += 2;
+ continue;
+ } else if (*cp == '/') {
+ *cp = '\\';
+ }
+ cp++;
+ }
+ if (StrChr(view_fname, ' '))
+ view_fname = quote_pathname(view_fname);
+
+ StrAllocCopy(me->viewer_command, pres->command);
+
+ me->end_command = mailcap_substitute(anchor, pres, view_fname);
+ me->remove_command = NULL;
+
+ return me;
+ }
+#endif
+ /*
+ * Check for a suffix.
+ * Save the file under a suitably suffixed name.
+ */
+ if (!strcasecomp(pres->rep->name, STR_HTML)) {
+ suffix = HTML_SUFFIX;
+ } else if (!strncasecomp(pres->rep->name, "text/", 5)) {
+ suffix = TEXT_SUFFIX;
+ } else if ((suffix = HTFileSuffix(pres->rep,
+ anchor->content_encoding)) == 0
+ || *suffix != '.') {
+ if (!strncasecomp(pres->rep->name, "application/", 12)) {
+ suffix = BIN_SUFFIX;
+ } else {
+ suffix = HTML_SUFFIX;
+ }
+ }
+ me->fp = LYOpenTemp(fnam, suffix, BIN_W);
+ }
+
+ if (!me->fp) {
+ HTAlert(CANNOT_OPEN_TEMP);
+ FREE(me);
+ return NULL;
+ }
+
+ StrAllocCopy(me->viewer_command, pres->command);
+ /*
+ * Make command to process file.
+ */
+ me->end_command = mailcap_substitute(anchor, pres, fnam);
+
+ /*
+ * Make command to delete file.
+ */
+ me->remove_command = NULL;
+ HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam);
+ HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1);
+
+ 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
+ */
+HTStream *HTSaveToFile(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ HTStream *ret_obj;
+ char fnam[LY_MAXPATH];
+ const char *suffix;
+ char *cp;
+ int c = 0;
+
+#ifdef VMS
+ BOOL IsBinary = TRUE;
+#endif
+
+ ret_obj = typecalloc(HTStream);
+
+ 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 == WWW_DOWNLOAD)
+ goto Prepend_BASE;
+ return ret_obj;
+ }
+
+ LYCancelDownload = FALSE;
+ if (HTOutputFormat != WWW_DOWNLOAD) {
+ if (traversal ||
+ (no_download && !override_no_download && no_disk_save)) {
+ if (!traversal) {
+ HTAlert(CANNOT_DISPLAY_FILE);
+ }
+ LYCancelDownload = TRUE;
+ if (traversal)
+ LYCancelledFetch = TRUE;
+ FREE(ret_obj);
+ return (NULL);
+ }
+
+ if (((cp = StrChr(pres->rep->name, ';')) != NULL) &&
+ strstr((cp + 1), "charset") != NULL) {
+ _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name);
+ } else if (*(pres->rep->name) != '\0') {
+ _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name);
+ } else {
+ _statusline(CANNOT_DISPLAY_FILE_D_OR_C);
+ }
+
+ while (c != 'D' && c != 'C' && !LYCharIsINTERRUPT(c)) {
+ c = LYgetch_single();
+#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 (c == 'C' || LYCharIsINTERRUPT(c)) {
+ _statusline(CANCELLING_FILE);
+ LYCancelDownload = TRUE;
+ FREE(ret_obj);
+ return (NULL);
+ }
+ }
+
+ /*
+ * Set up a 'D'ownload.
+ */
+ if (LYCachedTemp(fnam, &(anchor->FileCache))) {
+ /* This used to be LYNewBinFile(fnam); changed to a different call so
+ * that the open fp gets registered in the list keeping track of temp
+ * files, equivalent to when LYOpenTemp() gets called below. This
+ * avoids a file descriptor leak caused by LYCloseTempFP() not being
+ * able to find the fp. The binary suffix is expected to not be used,
+ * it's only for fallback in unusual error cases. - kw
+ */
+ ret_obj->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W);
+ } else {
+ /*
+ * Check for a suffix.
+ * Save the file under a suitably suffixed name.
+ */
+ if (!strcasecomp(pres->rep->name, STR_HTML)) {
+ suffix = HTML_SUFFIX;
+ } else if (!strncasecomp(pres->rep->name, "text/", 5)) {
+ suffix = TEXT_SUFFIX;
+ } else if (!strncasecomp(pres->rep->name, "application/", 12)) {
+ suffix = BIN_SUFFIX;
+ } else if ((suffix = HTFileSuffix(pres->rep,
+ anchor->content_encoding)) == 0
+ || *suffix != '.') {
+ suffix = HTML_SUFFIX;
+ }
+ ret_obj->fp = LYOpenTemp(fnam, suffix, BIN_W);
+ }
+
+ if (!ret_obj->fp) {
+ HTAlert(CANNOT_OPEN_OUTPUT);
+ FREE(ret_obj);
+ return NULL;
+ }
+
+ 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
+ */
+#ifdef VMS
+ IsBinary = FALSE;
+#endif
+
+ /*
+ * 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
+ */
+ HTInfoMsg2(CONTENT_TYPE_MSG, pres->rep->name);
+
+ StrAllocCopy(WWW_Download_File, fnam);
+
+ /*
+ * Make command to delete file.
+ */
+ ret_obj->remove_command = NULL;
+ HTAddParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1, fnam);
+ HTEndParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1);
+
+#ifdef VMS
+ if (IsBinary && UseFixedRecords) {
+ StrAllocCopy(ret_obj->end_command, "SaveVMSBinaryFile");
+ FIXED_RECORD_COMMAND = 0;
+ HTAddParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1, fnam);
+ HTEndParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1);
+
+ } else {
+#endif /* VMS */
+ StrAllocCopy(ret_obj->end_command, "SaveToFile");
+#ifdef VMS
+ }
+#endif /* VMS */
+
+ _statusline(RETRIEVING_FILE);
+
+ StrAllocCopy(anchor->FileCache, fnam);
+ Prepend_BASE:
+ if (LYPrependBaseToSource &&
+ !strncasecomp(pres->rep->name, STR_HTML, 9) &&
+ !anchor->content_encoding) {
+ /*
+ * 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
+ *
+ * Add timestamp (last reload).
+ */
+ char *temp = NULL;
+
+ if (non_empty(anchor->content_base)) {
+ StrAllocCopy(temp, anchor->content_base);
+ } else if (non_empty(anchor->content_location)) {
+ StrAllocCopy(temp, anchor->content_location);
+ }
+ if (temp) {
+ LYRemoveBlanks(temp);
+ if (!is_url(temp)) {
+ FREE(temp);
+ }
+ }
+
+ fprintf(ret_obj->fp,
+ "<!-- X-URL: %s -->\n", anchor->address);
+ if (non_empty(anchor->date)) {
+ fprintf(ret_obj->fp,
+ "<!-- Date: %s -->\n", anchor->date);
+ if (non_empty(anchor->last_modified)
+ && strcmp(anchor->last_modified, anchor->date)
+ && strcmp(anchor->last_modified,
+ "Thu, 01 Jan 1970 00:00:01 GMT")) {
+ fprintf(ret_obj->fp,
+ "<!-- Last-Modified: %s -->\n", anchor->last_modified);
+ }
+ }
+ fprintf(ret_obj->fp,
+ "<BASE HREF=\"%s\">\n\n", (temp ? temp : anchor->address));
+ FREE(temp);
+ }
+ if (LYPrependCharsetToSource &&
+ !strncasecomp(pres->rep->name, STR_HTML, 9) &&
+ !anchor->content_encoding) {
+ /*
+ * Add the document's charset as a META CHARSET tag at the top of the
+ * file, so HTTP charset header will not be forgotten when a document
+ * saved as local file. We add this line only(!) if HTTP charset
+ * present. - LP 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 (non_empty(anchor->charset)) {
+ StrAllocCopy(temp, anchor->charset);
+ LYRemoveBlanks(temp);
+ fprintf(ret_obj->fp,
+ "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"" STR_HTML
+ "; charset=%s\">\n\n",
+ temp);
+ }
+ FREE(temp);
+ }
+ return ret_obj;
+}
+
+/* Set up stream for uncompressing - FM
+ * -------------------------------
+ */
+HTStream *HTCompressed(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ HTStream *me;
+ HTFormat format;
+ char *type = NULL;
+ HTPresentation *Pres = NULL;
+ HTPresentation *Pnow = NULL;
+ int n, i;
+ BOOL can_present = FALSE;
+ char fnam[LY_MAXPATH];
+ char temp[LY_MAXPATH]; /* actually stores just a suffix */
+ const char *suffix;
+ char *uncompress_mask = NULL;
+ const char *compress_suffix = "";
+ const char *middle;
+
+ /*
+ * Deal with any inappropriate invocations of this function, or a download
+ * request, in which case we won't bother to uncompress the file. - FM
+ */
+ if (!(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(STR_BINARY);
+ me = HTStreamStack(format, pres->rep_out, sink, anchor);
+ return me;
+ }
+ n = HTList_count(HTPresentations);
+ for (i = 0; i < n; i++) {
+ Pnow = (HTPresentation *) HTList_objectAt(HTPresentations, i);
+ if (!strcasecomp(Pnow->rep->name, anchor->content_type) &&
+ Pnow->rep_out == WWW_PRESENT) {
+ const char *program = "";
+
+ /*
+ * Pick the best presentation. User-defined mappings are at the
+ * end of the list, and unless the quality is lower, we prefer
+ * those.
+ */
+ if (Pres == 0)
+ Pres = Pnow;
+ else if (Pres->quality > Pnow->quality)
+ continue;
+ else
+ Pres = Pnow;
+ /*
+ * We have a presentation mapping for it. - FM
+ */
+ can_present = TRUE;
+ switch (HTEncodingToCompressType(anchor->content_encoding)) {
+ case cftGzip:
+ if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
+ /*
+ * It's compressed with the modern gzip. - FM
+ */
+ StrAllocCopy(uncompress_mask, program);
+ StrAllocCat(uncompress_mask, " -d --no-name %s");
+ compress_suffix = "gz";
+ }
+ break;
+ case cftDeflate:
+ if ((program = HTGetProgramPath(ppINFLATE)) != NULL) {
+ /*
+ * It's compressed with a zlib wrapper.
+ */
+ StrAllocCopy(uncompress_mask, program);
+ StrAllocCat(uncompress_mask, " %s");
+ compress_suffix = "zz";
+ }
+ break;
+ case cftBzip2:
+ if ((program = HTGetProgramPath(ppBZIP2)) != NULL) {
+ StrAllocCopy(uncompress_mask, program);
+ StrAllocCat(uncompress_mask, " -d %s");
+ compress_suffix = "bz2";
+ }
+ break;
+ case cftBrotli:
+ if ((program = HTGetProgramPath(ppBROTLI)) != NULL) {
+ StrAllocCopy(uncompress_mask, program);
+ StrAllocCat(uncompress_mask, " -j -d %s");
+ compress_suffix = "br";
+ }
+ break;
+ case cftCompress:
+ if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) {
+ /*
+ * It's compressed the old fashioned Unix way. - FM
+ */
+ StrAllocCopy(uncompress_mask, program);
+ StrAllocCat(uncompress_mask, " %s");
+ compress_suffix = "Z";
+ }
+ break;
+ case cftNone:
+ break;
+ }
+ }
+ }
+ if (can_present == FALSE || /* no presentation mapping */
+ uncompress_mask == NULL || /* not gzip or compress */
+ StrChr(anchor->content_type, ';') || /* wrong charset */
+ HTOutputFormat == WWW_DOWNLOAD || /* download */
+ !strcasecomp(pres->rep_out->name, STR_DOWNLOAD) || /* download */
+ (traversal && /* only handle html or plain text for traversals */
+ strcasecomp(anchor->content_type, STR_HTML) &&
+ strcasecomp(anchor->content_type, STR_PLAINTEXT))) {
+ /*
+ * 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) {
+ /*
+ * Use "x-" prefix, none of the types we are likely to construct
+ * here are official. That is we generate "application/x-gzip" and
+ * so on. - kw
+ */
+ if (!strncasecomp(anchor->content_encoding, "x-", 2))
+ StrAllocCopy(type, "application/");
+ else
+ StrAllocCopy(type, "application/x-");
+ 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 = typecalloc(HTStream);
+ 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;
+#ifdef FNAMES_8_3
+ me->idash = FALSE;
+#endif
+
+ /*
+ * Remove any old versions of the file. - FM
+ */
+ if (anchor->FileCache) {
+ (void) LYRemoveTemp(anchor->FileCache);
+ FREE(anchor->FileCache);
+ }
+
+ /*
+ * Get a new temporary filename and substitute a suitable suffix. - FM
+ */
+ middle = NULL;
+ if (!strcasecomp(anchor->content_type, STR_HTML)) {
+ middle = HTML_SUFFIX;
+ middle++; /* point to 'h' of .htm(l) - kw */
+ } else if (!strncasecomp(anchor->content_type, "text/", 5)) {
+ middle = &TEXT_SUFFIX[1];
+ } else if (!strncasecomp(anchor->content_type, "application/", 12)) {
+ /* FIXME: why is this BEFORE HTFileSuffix? */
+ middle = &BIN_SUFFIX[1];
+ } else if ((suffix =
+ HTFileSuffix(HTAtom_for(anchor->content_type), NULL)) &&
+ *suffix == '.') {
+#if defined(VMS) || defined(FNAMES_8_3)
+ if (StrChr(suffix + 1, '.') == NULL)
+#endif
+ middle = suffix + 1;
+ }
+
+ temp[0] = 0; /* construct the suffix */
+ if (middle) {
+#ifdef FNAMES_8_3
+ me->idash = TRUE; /* remember position of '-' - kw */
+ strcat(temp, "-"); /* NAME-htm, NAME-txt, etc. - hack for DOS */
+#else
+ strcat(temp, "."); /* NAME.html, NAME-txt etc. */
+#endif /* FNAMES_8_3 */
+ strcat(temp, middle);
+#ifdef VMS
+ strcat(temp, "-"); /* NAME.html-gz, NAME.txt-gz, NAME.txt-Z etc. */
+#else
+ strcat(temp, "."); /* NAME-htm.gz (DOS), NAME.html.gz (UNIX)etc. */
+#endif /* VMS */
+ }
+ strcat(temp, compress_suffix);
+
+ /*
+ * Open the file for receiving the compressed input stream. - FM
+ */
+ me->fp = LYOpenTemp(fnam, temp, BIN_W);
+ if (!me->fp) {
+ HTAlert(CANNOT_OPEN_TEMP);
+ FREE(uncompress_mask);
+ FREE(me);
+ return NULL;
+ }
+
+ /*
+ * 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
+ */
+#ifdef USE_BROTLI
+ if (compress_suffix[0] == 'b' /* e.g., ".br" */
+ && compress_suffix[1] == 'r'
+ && !me->viewer_command) {
+ /*
+ * We won't call brotli externally, so we don't need to supply a
+ * command for it.
+ */
+ StrAllocCopy(me->end_command, "");
+ } else
+#endif
+#ifdef USE_BZLIB
+ if (compress_suffix[0] == 'b' /* must be bzip2 */
+ && compress_suffix[1] == 'z'
+ && !me->viewer_command) {
+ /*
+ * We won't call bzip2 externally, so we don't need to supply a command
+ * for it.
+ */
+ StrAllocCopy(me->end_command, "");
+ } else
+#endif
+#ifdef USE_ZLIB
+ /* FIXME: allow deflate here, e.g., 'z' */
+ if (compress_suffix[0] == 'g' /* must be gzip */
+ && !me->viewer_command) {
+ /*
+ * We won't call gzip or compress externally, so we don't need to
+ * supply a command for it.
+ */
+ StrAllocCopy(me->end_command, "");
+ } else
+#endif /* USE_ZLIB */
+ {
+ me->end_command = NULL;
+ HTAddParam(&(me->end_command), uncompress_mask, 1, fnam);
+ HTEndParam(&(me->end_command), uncompress_mask, 1);
+ }
+ FREE(uncompress_mask);
+
+ /*
+ * Make command to delete file. - FM
+ */
+ me->remove_command = NULL;
+ HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam);
+ HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1);
+
+ /*
+ * Save the filename and return the structure. - FM
+ */
+ StrAllocCopy(anchor->FileCache, fnam);
+ return me;
+}
+
+/* Dump output to stdout - LJM & FM
+ * ---------------------
+ *
+ */
+HTStream *HTDumpToStdout(HTPresentation *pres GCC_UNUSED,
+ HTParentAnchor *anchor,
+ HTStream *sink GCC_UNUSED)
+{
+ HTStream *ret_obj;
+
+ ret_obj = typecalloc(HTStream);
+
+ 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.
+ */
+static unsigned long LYVMS_FixedLengthRecords(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 structures */
+ /* 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 */