about summary refs log tree commit diff stats
path: root/WWW/Library/Implementation/HTFile.c
diff options
context:
space:
mode:
Diffstat (limited to 'WWW/Library/Implementation/HTFile.c')
-rw-r--r--WWW/Library/Implementation/HTFile.c3240
1 files changed, 3240 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c
new file mode 100644
index 00000000..be561e44
--- /dev/null
+++ b/WWW/Library/Implementation/HTFile.c
@@ -0,0 +1,3240 @@
+/*
+ * $LynxId: HTFile.c,v 1.129 2012/02/10 01:10:51 tom Exp $
+ *
+ *			File Access				HTFile.c
+ *			===========
+ *
+ *	This is unix-specific code in general, with some VMS bits.
+ *	These are routines for file access used by browsers.
+ *	Development of this module for Unix DIRED_SUPPORT in Lynx
+ *	 regrettably has has been conducted in a manner with now
+ *	 creates a major impediment for hopes of adapting Lynx to
+ *	 a newer version of the library.
+ *
+ *  History:
+ *	   Feb 91	Written Tim Berners-Lee CERN/CN
+ *	   Apr 91	vms-vms access included using DECnet syntax
+ *	26 Jun 92 (JFG) When running over DECnet, suppressed FTP.
+ *			Fixed access bug for relative names on VMS.
+ *	   Sep 93 (MD)	Access to VMS files allows sharing.
+ *	15 Nov 93 (MD)	Moved HTVMSname to HTVMSUTILS.C
+ *	27 Dec 93 (FM)	FTP now works with VMS hosts.
+ *			FTP path must be Unix-style and cannot include
+ *			the device or top directory.
+ */
+
+#include <HTUtils.h>
+
+#ifndef VMS
+#if defined(DOSPATH)
+#undef LONG_LIST
+#define LONG_LIST		/* Define this for long style unix listings (ls -l),
+				   the actual style is configurable from lynx.cfg */
+#endif
+/* #define NO_PARENT_DIR_REFERENCE */
+/* Define this for no parent links */
+#endif /* !VMS */
+
+#if defined(DOSPATH)
+#define HAVE_READDIR 1
+#define USE_DIRENT
+#endif
+
+#if defined(USE_DOS_DRIVES)
+#include <HTDOS.h>
+#endif
+
+#include <HTFile.h>		/* Implemented here */
+
+#ifdef VMS
+#include <stat.h>
+#endif /* VMS */
+
+#if defined (USE_ZLIB) || defined (USE_BZLIB)
+#include <GridText.h>
+#endif
+
+#define MULTI_SUFFIX ".multi"	/* Extension for scanning formats */
+
+#include <HTParse.h>
+#include <HTTCP.h>
+#ifndef DECNET
+#include <HTFTP.h>
+#endif /* !DECNET */
+#include <HTAnchor.h>
+#include <HTAtom.h>
+#include <HTAAProt.h>
+#include <HTFWriter.h>
+#include <HTInit.h>
+#include <HTBTree.h>
+#include <HTAlert.h>
+#include <HTCJK.h>
+#include <UCDefs.h>
+#include <UCMap.h>
+#include <UCAux.h>
+
+#include <LYexit.h>
+#include <LYCharSets.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYUtils.h>
+
+#ifdef USE_PRETTYSRC
+# include <LYPrettySrc.h>
+#endif
+
+#include <LYLeaks.h>
+
+typedef struct _HTSuffix {
+    char *suffix;
+    HTAtom *rep;
+    HTAtom *encoding;
+    char *desc;
+    float quality;
+} HTSuffix;
+
+typedef struct {
+    struct stat file_info;
+    char sort_tags;
+    char file_name[1];		/* on the end of the struct, since its length varies */
+} DIRED;
+
+#ifndef NGROUPS
+#ifdef NGROUPS_MAX
+#define NGROUPS NGROUPS_MAX
+#else
+#define NGROUPS 32
+#endif /* NGROUPS_MAX */
+#endif /* NGROUPS */
+
+#ifndef GETGROUPS_T
+#define GETGROUPS_T int
+#endif
+
+#include <HTML.h>		/* For directory object building */
+
+#define PUTC(c)      (*target->isa->put_character)(target, c)
+#define PUTS(s)      (*target->isa->put_string)(target, s)
+#define START(e)     (*target->isa->start_element)(target, e, 0, 0, -1, 0)
+#define END(e)       (*target->isa->end_element)(target, e, 0)
+#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
+			(*target->isa->end_element)(target, e, 0)
+#define FREE_TARGET  (*target->isa->_free)(target)
+#define ABORT_TARGET (*targetClass._abort)(target, NULL);
+
+struct _HTStructured {
+    const HTStructuredClass *isa;
+    /* ... */
+};
+
+/*
+ *  Controlling globals.
+ */
+int HTDirAccess = HT_DIR_OK;
+
+#ifdef DIRED_SUPPORT
+int HTDirReadme = HT_DIR_README_NONE;
+
+#else
+int HTDirReadme = HT_DIR_README_TOP;
+#endif /* DIRED_SUPPORT */
+
+static const char *HTMountRoot = "/Net/";	/* Where to find mounts */
+
+#ifdef VMS
+static const char *HTCacheRoot = "/WWW$SCRATCH";	/* Where to cache things */
+
+#else
+static const char *HTCacheRoot = "/tmp/W3_Cache_";	/* Where to cache things */
+#endif /* VMS */
+
+static char s_no_suffix[] = "*";
+static char s_unknown_suffix[] = "*.*";
+
+/*
+ *  Suffix registration.
+ */
+static HTList *HTSuffixes = 0;
+
+static HTSuffix no_suffix =
+{
+    s_no_suffix, NULL, NULL, NULL, 1.0
+};
+
+static HTSuffix unknown_suffix =
+{
+    s_unknown_suffix, NULL, NULL, NULL, 1.0
+};
+
+/*	To free up the suffixes at program exit.
+ *	----------------------------------------
+ */
+#ifdef LY_FIND_LEAKS
+static void free_suffixes(void);
+#endif
+
+#ifdef LONG_LIST
+static char *FormatStr(char **bufp,
+		       char *start,
+		       const char *entry)
+{
+    char fmt[512];
+
+    if (*start) {
+	sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start);
+	HTSprintf0(bufp, fmt, entry);
+    } else if (*bufp && !(entry && *entry)) {
+	**bufp = '\0';
+    } else if (entry) {
+	StrAllocCopy(*bufp, entry);
+    }
+    return *bufp;
+}
+
+static char *FormatNum(char **bufp,
+		       char *start,
+		       int entry)
+{
+    char fmt[512];
+
+    if (*start) {
+	sprintf(fmt, "%%%.*sd", (int) sizeof(fmt) - 3, start);
+	HTSprintf0(bufp, fmt, entry);
+    } else {
+	sprintf(fmt, "%d", entry);
+	StrAllocCopy(*bufp, fmt);
+    }
+    return *bufp;
+}
+
+static void LYListFmtParse(const char *fmtstr,
+			   DIRED * data,
+			   char *file,
+			   HTStructured * target,
+			   char *tail)
+{
+    char c;
+    char *s;
+    char *end;
+    char *start;
+    char *str = NULL;
+    char *buf = NULL;
+    char tmp[LY_MAXPATH];
+    char type;
+
+#ifndef NOUSERS
+    const char *name;
+#endif
+    time_t now;
+    char *datestr;
+
+#ifdef S_IFLNK
+    int len;
+#endif
+#define SEC_PER_YEAR	(60 * 60 * 24 * 365)
+
+#ifdef _WINDOWS			/* 1998/01/06 (Tue) 21:20:53 */
+    static const char *pbits[] =
+    {
+	"---", "--x", "-w-", "-wx",
+	"r--", "r-x", "rw-", "rwx",
+	0};
+
+#define PBIT(a, n, s)  pbits[((a) >> (n)) & 0x7]
+
+#else
+    static const char *pbits[] =
+    {"---", "--x", "-w-", "-wx",
+     "r--", "r-x", "rw-", "rwx", 0};
+    static const char *psbits[] =
+    {"--S", "--s", "-wS", "-ws",
+     "r-S", "r-s", "rwS", "rws", 0};
+
+#define PBIT(a, n, s)  (s) ? psbits[((a) >> (n)) & 0x7] : \
+	pbits[((a) >> (n)) & 0x7]
+#endif
+#if defined(S_ISVTX) && !defined(_WINDOWS)
+    static const char *ptbits[] =
+    {"--T", "--t", "-wT", "-wt",
+     "r-T", "r-t", "rwT", "rwt", 0};
+
+#define PTBIT(a, s)  (s) ? ptbits[(a) & 0x7] : pbits[(a) & 0x7]
+#else
+#define PTBIT(a, s)  PBIT(a, 0, 0)
+#endif
+
+    if (data->file_info.st_mode == 0)
+	fmtstr = "    %a";	/* can't stat so just do anchor */
+
+    StrAllocCopy(str, fmtstr);
+    s = str;
+    end = str + strlen(str);
+    while (*s) {
+	start = s;
+	while (*s) {
+	    if (*s == '%') {
+		if (*(s + 1) == '%')	/* literal % */
+		    s++;
+		else
+		    break;
+	    }
+	    s++;
+	}
+	/* s is positioned either at a % or at \0 */
+	*s = '\0';
+	if (s > start) {	/* some literal chars. */
+	    PUTS(start);
+	}
+	if (s == end)
+	    break;
+	start = ++s;
+	while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' ||
+	       *s == '#' || *s == '+' || *s == '\'')
+	    s++;
+	c = *s;			/* the format char. or \0 */
+	*s = '\0';
+
+	switch (c) {
+	case '\0':
+	    PUTS(start);
+	    continue;
+
+	case 'A':
+	case 'a':		/* anchor */
+	    HTDirEntry(target, tail, data->file_name);
+	    FormatStr(&buf, start, data->file_name);
+	    PUTS(buf);
+	    END(HTML_A);
+	    *buf = '\0';
+#ifdef S_IFLNK
+	    if (c != 'A' && S_ISLNK(data->file_info.st_mode) &&
+		(len = (int) readlink(file, tmp, sizeof(tmp) - 1)) >= 0) {
+		PUTS(" -> ");
+		tmp[len] = '\0';
+		PUTS(tmp);
+	    }
+#endif
+	    break;
+
+	case 'T':		/* MIME type */
+	case 't':		/* MIME type description */
+	    if (S_ISDIR(data->file_info.st_mode)) {
+		if (c != 'T') {
+		    FormatStr(&buf, start, ENTRY_IS_DIRECTORY);
+		} else {
+		    FormatStr(&buf, start, "");
+		}
+	    } else {
+		const char *cp2;
+		HTFormat format;
+
+		format = HTFileFormat(file, NULL, &cp2);
+
+		if (c != 'T') {
+		    if (cp2 == NULL) {
+			if (!StrNCmp(HTAtom_name(format),
+				     "application", 11)) {
+			    cp2 = HTAtom_name(format) + 12;
+			    if (!StrNCmp(cp2, "x-", 2))
+				cp2 += 2;
+			} else {
+			    cp2 = HTAtom_name(format);
+			}
+		    }
+		    FormatStr(&buf, start, cp2);
+		} else {
+		    FormatStr(&buf, start, HTAtom_name(format));
+		}
+	    }
+	    break;
+
+	case 'd':		/* date */
+	    now = time(0);
+	    datestr = ctime(&data->file_info.st_mtime);
+	    if ((now - data->file_info.st_mtime) < SEC_PER_YEAR / 2)
+		/*
+		 * MMM DD HH:MM
+		 */
+		sprintf(tmp, "%.12s", datestr + 4);
+	    else
+		/*
+		 * MMM DD YYYY
+		 */
+		sprintf(tmp, "%.7s %.4s ", datestr + 4,
+			datestr + 20);
+	    FormatStr(&buf, start, tmp);
+	    break;
+
+	case 's':		/* size in bytes */
+	    FormatNum(&buf, start, (int) data->file_info.st_size);
+	    break;
+
+	case 'K':		/* size in Kilobytes but not for directories */
+	    if (S_ISDIR(data->file_info.st_mode)) {
+		FormatStr(&buf, start, "");
+		StrAllocCat(buf, " ");
+		break;
+	    }
+	    /* FALL THROUGH */
+	case 'k':		/* size in Kilobytes */
+	    FormatNum(&buf, start, (int) ((data->file_info.st_size + 1023) / 1024));
+	    StrAllocCat(buf, "K");
+	    break;
+
+	case 'p':		/* unix-style permission bits */
+	    switch (data->file_info.st_mode & S_IFMT) {
+#if defined(_MSC_VER) && defined(_S_IFIFO)
+	    case _S_IFIFO:
+		type = 'p';
+		break;
+#else
+	    case S_IFIFO:
+		type = 'p';
+		break;
+#endif
+	    case S_IFCHR:
+		type = 'c';
+		break;
+	    case S_IFDIR:
+		type = 'd';
+		break;
+	    case S_IFREG:
+		type = '-';
+		break;
+#ifdef S_IFBLK
+	    case S_IFBLK:
+		type = 'b';
+		break;
+#endif
+#ifdef S_IFLNK
+	    case S_IFLNK:
+		type = 'l';
+		break;
+#endif
+#ifdef S_IFSOCK
+# ifdef S_IFIFO			/* some older machines (e.g., apollo) have a conflict */
+#  if S_IFIFO != S_IFSOCK
+	    case S_IFSOCK:
+		type = 's';
+		break;
+#  endif
+# else
+	    case S_IFSOCK:
+		type = 's';
+		break;
+# endif
+#endif /* S_IFSOCK */
+	    default:
+		type = '?';
+		break;
+	    }
+#ifdef _WINDOWS
+	    sprintf(tmp, "%c%s", type,
+		    PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_IRWXU));
+#else
+	    sprintf(tmp, "%c%s%s%s", type,
+		    PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_ISUID),
+		    PBIT(data->file_info.st_mode, 3, data->file_info.st_mode & S_ISGID),
+		    PTBIT(data->file_info.st_mode, data->file_info.st_mode & S_ISVTX));
+#endif
+	    FormatStr(&buf, start, tmp);
+	    break;
+
+	case 'o':		/* owner */
+#ifndef NOUSERS
+	    name = HTAA_UidToName((int) data->file_info.st_uid);
+	    if (*name) {
+		FormatStr(&buf, start, name);
+	    } else {
+		FormatNum(&buf, start, (int) data->file_info.st_uid);
+	    }
+#endif
+	    break;
+
+	case 'g':		/* group */
+#ifndef NOUSERS
+	    name = HTAA_GidToName((int) data->file_info.st_gid);
+	    if (*name) {
+		FormatStr(&buf, start, name);
+	    } else {
+		FormatNum(&buf, start, (int) data->file_info.st_gid);
+	    }
+#endif
+	    break;
+
+	case 'l':		/* link count */
+	    FormatNum(&buf, start, (int) data->file_info.st_nlink);
+	    break;
+
+	case '%':		/* literal % with flags/width */
+	    FormatStr(&buf, start, "%");
+	    break;
+
+	default:
+	    fprintf(stderr,
+		    "Unknown format character `%c' in list format\n", c);
+	    break;
+	}
+	if (buf)
+	    PUTS(buf);
+
+	s++;
+    }
+    FREE(buf);
+    PUTC('\n');
+    FREE(str);
+}
+#endif /* LONG_LIST */
+
+/*	Define the representation associated with a file suffix.
+ *	--------------------------------------------------------
+ *
+ *	Calling this with suffix set to "*" will set the default
+ *	representation.
+ *	Calling this with suffix set to "*.*" will set the default
+ *	representation for unknown suffix files which contain a ".".
+ *
+ *	The encoding parameter can give a trivial (8bit, 7bit, binary)
+ *	or real (gzip, compress) encoding.
+ *
+ *	If filename suffix is already defined with the same encoding
+ *	its previous definition is overridden.
+ */
+void HTSetSuffix5(const char *suffix,
+		  const char *representation,
+		  const char *encoding,
+		  const char *desc,
+		  double value)
+{
+    HTSuffix *suff;
+    BOOL trivial_enc = (BOOL) IsUnityEncStr(encoding);
+
+    if (strcmp(suffix, s_no_suffix) == 0)
+	suff = &no_suffix;
+    else if (strcmp(suffix, s_unknown_suffix) == 0)
+	suff = &unknown_suffix;
+    else {
+	HTList *cur = HTSuffixes;
+
+	while (NULL != (suff = (HTSuffix *) HTList_nextObject(cur))) {
+	    if (suff->suffix && 0 == strcmp(suff->suffix, suffix) &&
+		((trivial_enc && IsUnityEnc(suff->encoding)) ||
+		 (!trivial_enc && !IsUnityEnc(suff->encoding) &&
+		  strcmp(encoding, HTAtom_name(suff->encoding)) == 0)))
+		break;
+	}
+	if (!suff) {		/* Not found -- create a new node */
+	    suff = typecalloc(HTSuffix);
+	    if (suff == NULL)
+		outofmem(__FILE__, "HTSetSuffix");
+
+	    assert(suff != NULL);
+
+	    if (!HTSuffixes) {
+		HTSuffixes = HTList_new();
+#ifdef LY_FIND_LEAKS
+		atexit(free_suffixes);
+#endif
+	    }
+
+	    HTList_addObject(HTSuffixes, suff);
+
+	    StrAllocCopy(suff->suffix, suffix);
+	}
+    }
+
+    if (representation)
+	suff->rep = HTAtom_for(representation);
+
+    /*
+     * Memory leak fixed.
+     * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
+     * Invariant code removed.
+     */
+    suff->encoding = HTAtom_for(encoding);
+
+    StrAllocCopy(suff->desc, desc);
+
+    suff->quality = (float) value;
+}
+
+#ifdef LY_FIND_LEAKS
+/*
+ *	Purpose:	Free all added suffixes.
+ *	Arguments:	void
+ *	Return Value:	void
+ *	Remarks/Portability/Dependencies/Restrictions:
+ *		To be used at program exit.
+ *	Revision History:
+ *		05-28-94	created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void free_suffixes(void)
+{
+    HTSuffix *suff = NULL;
+
+    /*
+     * Loop through all suffixes.
+     */
+    while (!HTList_isEmpty(HTSuffixes)) {
+	/*
+	 * Free off each item and its members if need be.
+	 */
+	suff = (HTSuffix *) HTList_removeLastObject(HTSuffixes);
+	FREE(suff->suffix);
+	FREE(suff->desc);
+	FREE(suff);
+    }
+    /*
+     * Free off the list itself.
+     */
+    HTList_delete(HTSuffixes);
+    HTSuffixes = NULL;
+}
+#endif /* LY_FIND_LEAKS */
+
+/*	Make the cache file name for a W3 document.
+ *	-------------------------------------------
+ *	Make up a suitable name for saving the node in
+ *
+ *	E.g.	/tmp/WWW_Cache_news/1234@cernvax.cern.ch
+ *		/tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx
+ *
+ *  On exit:
+ *	Returns a malloc'ed string which must be freed by the caller.
+ */
+char *HTCacheFileName(const char *name)
+{
+    char *acc_method = HTParse(name, "", PARSE_ACCESS);
+    char *host = HTParse(name, "", PARSE_HOST);
+    char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
+    char *result = NULL;
+
+    HTSprintf0(&result, "%s/WWW/%s/%s%s", HTCacheRoot, acc_method, host, path);
+
+    FREE(path);
+    FREE(acc_method);
+    FREE(host);
+    return result;
+}
+
+/*	Open a file for write, creating the path.
+ *	-----------------------------------------
+ */
+#ifdef NOT_IMPLEMENTED
+static int HTCreatePath(const char *path)
+{
+    return -1;
+}
+#endif /* NOT_IMPLEMENTED */
+
+/*	Convert filename from URL-path syntax to local path format
+ *	----------------------------------------------------------
+ *	Input name is assumed to be the URL-path of a local file
+ *      URL, i.e. what comes after the "file://localhost".
+ *      '#'-fragments to be treated as such must already be stripped.
+ *      If expand_all is FALSE, unescape only escaped '/'. - kw
+ *
+ *  On exit:
+ *	Returns a malloc'ed string which must be freed by the caller.
+ */
+char *HTURLPath_toFile(const char *name,
+		       int expand_all,
+		       int is_remote GCC_UNUSED)
+{
+    char *path = NULL;
+    char *result = NULL;
+
+    StrAllocCopy(path, name);
+    if (expand_all)
+	HTUnEscape(path);	/* Interpret all % signs */
+    else
+	HTUnEscapeSome(path, "/");	/* Interpret % signs for path delims */
+
+    CTRACE((tfp, "URLPath `%s' means path `%s'\n", name, path));
+#if defined(USE_DOS_DRIVES)
+    StrAllocCopy(result, is_remote ? path : HTDOS_name(path));
+#else
+    StrAllocCopy(result, path);
+#endif
+
+    FREE(path);
+
+    return result;
+}
+/*	Convert filenames between local and WWW formats.
+ *	------------------------------------------------
+ *	Make up a suitable name for saving the node in
+ *
+ *	E.g.	$(HOME)/WWW/news/1234@cernvax.cern.ch
+ *		$(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx
+ *
+ *  On exit:
+ *	Returns a malloc'ed string which must be freed by the caller.
+ */
+/* NOTE: Don't use this function if you know that the input is a URL path
+	 rather than a full URL, use HTURLPath_toFile instead.  Otherwise
+	 this function will return the wrong thing for some unusual
+	 paths (like ones containing "//", possibly escaped). - kw
+*/
+char *HTnameOfFile_WWW(const char *name,
+		       int WWW_prefix,
+		       int expand_all)
+{
+    char *acc_method = HTParse(name, "", PARSE_ACCESS);
+    char *host = HTParse(name, "", PARSE_HOST);
+    char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
+    const char *home;
+    char *result = NULL;
+
+    if (expand_all) {
+	HTUnEscape(path);	/* Interpret all % signs */
+    } else
+	HTUnEscapeSome(path, "/");	/* Interpret % signs for path delims */
+
+    if (0 == strcmp(acc_method, "file")		/* local file */
+	||!*acc_method) {	/* implicitly local? */
+	if ((0 == strcasecomp(host, HTHostName())) ||
+	    (0 == strcasecomp(host, "localhost")) || !*host) {
+	    CTRACE((tfp, "Node `%s' means path `%s'\n", name, path));
+	    StrAllocCopy(result, HTSYS_name(path));
+	} else if (WWW_prefix) {
+	    HTSprintf0(&result, "%s%s%s", "/Net/", host, path);
+	    CTRACE((tfp, "Node `%s' means file `%s'\n", name, result));
+	} else {
+	    StrAllocCopy(result, path);
+	}
+    } else if (WWW_prefix) {	/* other access */
+#ifdef VMS
+	if ((home = LYGetEnv("HOME")) == NULL)
+	    home = HTCacheRoot;
+	else
+	    home = HTVMS_wwwName(home);
+#else
+#if defined(_WINDOWS)		/* 1997/10/16 (Thu) 20:42:51 */
+	home = Home_Dir();
+#else
+	home = LYGetEnv("HOME");
+#endif
+	if (home == NULL)
+	    home = "/tmp";
+#endif /* VMS */
+	HTSprintf0(&result, "%s/WWW/%s/%s%s", home, acc_method, host, path);
+    } else {
+	StrAllocCopy(result, path);
+    }
+
+    FREE(host);
+    FREE(path);
+    FREE(acc_method);
+
+    CTRACE((tfp, "HTnameOfFile_WWW(%s,%d,%d) = %s\n",
+	    name, WWW_prefix, expand_all, result));
+
+    return result;
+}
+
+/*	Make a WWW name from a full local path name.
+ *	--------------------------------------------
+ *
+ *  Bugs:
+ *	At present, only the names of two network root nodes are hand-coded
+ *	in and valid for the NeXT only.  This should be configurable in
+ *	the general case.
+ */
+char *WWW_nameOfFile(const char *name)
+{
+    char *result = NULL;
+
+#ifdef NeXT
+    if (0 == StrNCmp("/private/Net/", name, 13)) {
+	HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 13);
+    } else
+#endif /* NeXT */
+    if (0 == StrNCmp(HTMountRoot, name, 5)) {
+	HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 5);
+    } else {
+	HTSprintf0(&result, "%s//%s%s", STR_FILE_URL, HTHostName(), name);
+    }
+    CTRACE((tfp, "File `%s'\n\tmeans node `%s'\n", name, result));
+    return result;
+}
+
+/*	Determine a suitable suffix, given the representation.
+ *	------------------------------------------------------
+ *
+ *  On entry,
+ *	rep	is the atomized MIME style representation
+ *	enc	is an encoding, trivial (8bit, binary, etc.) or gzip etc.
+ *
+ *  On exit:
+ *	Returns a pointer to a suitable suffix string if one has been
+ *	found, else "".
+ */
+const char *HTFileSuffix(HTAtom *rep,
+			 const char *enc)
+{
+    HTSuffix *suff;
+
+#ifdef FNAMES_8_3
+    HTSuffix *first_found = NULL;
+#endif
+    BOOL trivial_enc;
+    int n;
+    int i;
+
+#define NO_INIT			/* don't init anymore since I do it in Lynx at startup */
+#ifndef NO_INIT
+    if (!HTSuffixes)
+	HTFileInit();
+#endif /* !NO_INIT */
+
+    trivial_enc = (BOOL) IsUnityEncStr(enc);
+    n = HTList_count(HTSuffixes);
+    for (i = 0; i < n; i++) {
+	suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i);
+	if (suff->rep == rep &&
+#if defined(VMS) || defined(FNAMES_8_3)
+	/*  Don't return a suffix whose first char is a dot, and which
+	   has more dots or asterisks after that, for
+	   these systems - kw */
+	    (!suff->suffix || !suff->suffix[0] || suff->suffix[0] != '.' ||
+	     (strchr(suff->suffix + 1, '.') == NULL &&
+	      strchr(suff->suffix + 1, '*') == NULL)) &&
+#endif
+	    ((trivial_enc && IsUnityEnc(suff->encoding)) ||
+	     (!trivial_enc && !IsUnityEnc(suff->encoding) &&
+	      strcmp(enc, HTAtom_name(suff->encoding)) == 0))) {
+#ifdef FNAMES_8_3
+	    if (suff->suffix && (strlen(suff->suffix) <= 4)) {
+		/*
+		 * If length of suffix (including dot) is 4 or smaller, return
+		 * this one even if we found a longer one earlier - kw
+		 */
+		return suff->suffix;
+	    } else if (!first_found) {
+		first_found = suff;	/* remember this one */
+	    }
+#else
+	    return suff->suffix;	/* OK -- found */
+#endif
+	}
+    }
+#ifdef FNAMES_8_3
+    if (first_found)
+	return first_found->suffix;
+#endif
+    return "";			/* Dunno */
+}
+
+/*
+ * Trim version from VMS filenames to avoid confusing comparisons.
+ */
+#ifdef VMS
+static const char *VMS_trim_version(const char *filename)
+{
+    const char *result = filename;
+    const char *version = strchr(filename, ';');
+
+    if (version != 0) {
+	static char *stripped;
+
+	StrAllocCopy(stripped, filename);
+	stripped[version - filename] = '\0';
+	result = (const char *) stripped;
+    }
+    return result;
+}
+#define VMS_DEL_VERSION(name) name = VMS_trim_version(name)
+#else
+#define VMS_DEL_VERSION(name)	/* nothing */
+#endif
+
+/*	Determine file format from file name.
+ *	-------------------------------------
+ *
+ *	This version will return the representation and also set
+ *	a variable for the encoding.
+ *
+ *	Encoding may be a unity encoding (binary, 8bit, etc.) or
+ *	a content-coding like gzip, compress.
+ *
+ *	It will handle for example  x.txt, x.txt,Z, x.Z
+ */
+HTFormat HTFileFormat(const char *filename,
+		      HTAtom **pencoding,
+		      const char **pdesc)
+{
+    HTSuffix *suff;
+    int n;
+    int i;
+    int lf;
+
+    VMS_DEL_VERSION(filename);
+
+    if (pencoding)
+	*pencoding = NULL;
+    if (pdesc)
+	*pdesc = NULL;
+    if (LYforce_HTML_mode) {
+	if (pencoding)
+	    *pencoding = WWW_ENC_8BIT;
+	return WWW_HTML;
+    }
+#ifndef NO_INIT
+    if (!HTSuffixes)
+	HTFileInit();
+#endif /* !NO_INIT */
+    lf = (int) strlen(filename);
+    n = HTList_count(HTSuffixes);
+    for (i = 0; i < n; i++) {
+	int ls;
+
+	suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i);
+	ls = (int) strlen(suff->suffix);
+	if ((ls <= lf) && 0 == strcasecomp(suff->suffix, filename + lf - ls)) {
+	    int j;
+
+	    if (pencoding)
+		*pencoding = suff->encoding;
+	    if (pdesc)
+		*pdesc = suff->desc;
+	    if (suff->rep) {
+		return suff->rep;	/* OK -- found */
+	    }
+	    for (j = 0; j < n; j++) {	/* Got encoding, need representation */
+		int ls2;
+
+		suff = (HTSuffix *) HTList_objectAt(HTSuffixes, j);
+		ls2 = (int) strlen(suff->suffix);
+		if ((ls + ls2 <= lf) &&
+		    !strncasecomp(suff->suffix,
+				  filename + lf - ls - ls2, ls2)) {
+		    if (suff->rep) {
+			if (pdesc && !(*pdesc))
+			    *pdesc = suff->desc;
+			if (pencoding && IsUnityEnc(*pencoding) &&
+			    *pencoding != WWW_ENC_7BIT &&
+			    !IsUnityEnc(suff->encoding))
+			    *pencoding = suff->encoding;
+			return suff->rep;
+		    }
+		}
+	    }
+
+	}
+    }
+
+    /* defaults tree */
+
+    suff = (strchr(filename, '.')
+	    ? (unknown_suffix.rep
+	       ? &unknown_suffix
+	       : &no_suffix)
+	    : &no_suffix);
+
+    /*
+     * Set default encoding unless found with suffix already.
+     */
+    if (pencoding && !*pencoding) {
+	*pencoding = (suff->encoding
+		      ? suff->encoding
+		      : HTAtom_for("binary"));
+    }
+    return suff->rep ? suff->rep : WWW_BINARY;
+}
+
+/*	Revise the file format in relation to the Lynx charset. - FM
+ *	-------------------------------------------------------
+ *
+ *	This checks the format associated with an anchor for
+ *	an extended MIME Content-Type, and if a charset is
+ *	indicated, sets Lynx up for proper handling in relation
+ *	to the currently selected character set. - FM
+ */
+HTFormat HTCharsetFormat(HTFormat format,
+			 HTParentAnchor *anchor,
+			 int default_LYhndl)
+{
+    char *cp = NULL, *cp1, *cp2, *cp3 = NULL, *cp4;
+    BOOL chartrans_ok = FALSE;
+    int chndl = -1;
+
+    FREE(anchor->charset);
+    StrAllocCopy(cp, format->name);
+    LYLowerCase(cp);
+    if (((cp1 = strchr(cp, ';')) != NULL) &&
+	(cp2 = strstr(cp1, "charset")) != NULL) {
+	CTRACE((tfp, "HTCharsetFormat: Extended MIME Content-Type is %s\n",
+		format->name));
+	cp2 += 7;
+	while (*cp2 == ' ' || *cp2 == '=')
+	    cp2++;
+	StrAllocCopy(cp3, cp2);	/* copy to mutilate more */
+	for (cp4 = cp3; (*cp4 != '\0' && *cp4 != '"' &&
+			 *cp4 != ';' && *cp4 != ':' &&
+			 !WHITE(*cp4)); cp4++) {
+	    ;			/* do nothing */
+	}
+	*cp4 = '\0';
+	cp4 = cp3;
+	chndl = UCGetLYhndl_byMIME(cp3);
+	if (UCCanTranslateFromTo(chndl, current_char_set)) {
+	    chartrans_ok = YES;
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, cp4);
+	    HTAnchor_setUCInfoStage(anchor, chndl,
+				    UCT_STAGE_MIME,
+				    UCT_SETBY_MIME);
+	} else if (chndl < 0) {
+	    /*
+	     * Got something but we don't recognize it.
+	     */
+	    chndl = UCLYhndl_for_unrec;
+	    if (chndl < 0)
+		/*
+		 * UCLYhndl_for_unrec not defined :-( fallback to
+		 * UCLYhndl_for_unspec which always valid.
+		 */
+		chndl = UCLYhndl_for_unspec;	/* always >= 0 */
+	    if (UCCanTranslateFromTo(chndl, current_char_set)) {
+		chartrans_ok = YES;
+		HTAnchor_setUCInfoStage(anchor, chndl,
+					UCT_STAGE_MIME,
+					UCT_SETBY_DEFAULT);
+	    }
+	}
+	if (chartrans_ok) {
+	    LYUCcharset *p_in = HTAnchor_getUCInfoStage(anchor,
+							UCT_STAGE_MIME);
+	    LYUCcharset *p_out = HTAnchor_setUCInfoStage(anchor,
+							 current_char_set,
+							 UCT_STAGE_HTEXT,
+							 UCT_SETBY_DEFAULT);
+
+	    if (!p_out) {
+		/*
+		 * Try again.
+		 */
+		p_out = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT);
+	    }
+	    if (!strcmp(p_in->MIMEname, "x-transparent")) {
+		HTPassEightBitRaw = TRUE;
+		HTAnchor_setUCInfoStage(anchor,
+					HTAnchor_getUCLYhndl(anchor,
+							     UCT_STAGE_HTEXT),
+					UCT_STAGE_MIME,
+					UCT_SETBY_DEFAULT);
+	    }
+	    if (!strcmp(p_out->MIMEname, "x-transparent")) {
+		HTPassEightBitRaw = TRUE;
+		HTAnchor_setUCInfoStage(anchor,
+					HTAnchor_getUCLYhndl(anchor,
+							     UCT_STAGE_MIME),
+					UCT_STAGE_HTEXT,
+					UCT_SETBY_DEFAULT);
+	    }
+	    if (p_in->enc != UCT_ENC_CJK) {
+		HTCJK = NOCJK;
+		if (!(p_in->codepoints &
+		      UCT_CP_SUBSETOF_LAT1) &&
+		    chndl == current_char_set) {
+		    HTPassEightBitRaw = TRUE;
+		}
+	    } else if (p_out->enc == UCT_ENC_CJK) {
+		Set_HTCJK(p_in->MIMEname, p_out->MIMEname);
+	    }
+	} else {
+	    /*
+	     * Cannot translate.  If according to some heuristic the given
+	     * charset and the current display character both are likely to be
+	     * like ISO-8859 in structure, pretend we have some kind of match.
+	     */
+	    BOOL given_is_8859 = (BOOL) (!StrNCmp(cp4, "iso-8859-", 9) &&
+					 isdigit(UCH(cp4[9])));
+	    BOOL given_is_8859like = (BOOL) (given_is_8859 ||
+					     !StrNCmp(cp4, "windows-", 8) ||
+					     !StrNCmp(cp4, "cp12", 4) ||
+					     !StrNCmp(cp4, "cp-12", 5));
+	    BOOL given_and_display_8859like = (BOOL) (given_is_8859like &&
+						      (strstr(LYchar_set_names[current_char_set],
+							      "ISO-8859") ||
+						       strstr(LYchar_set_names[current_char_set],
+							      "windows-")));
+
+	    if (given_and_display_8859like) {
+		*cp1 = '\0';
+		format = HTAtom_for(cp);
+	    }
+	    if (given_is_8859) {
+		cp1 = &cp4[10];
+		while (*cp1 &&
+		       isdigit(UCH(*cp1)))
+		    cp1++;
+		*cp1 = '\0';
+	    }
+	    if (given_and_display_8859like) {
+		StrAllocCopy(anchor->charset, cp4);
+		HTPassEightBitRaw = TRUE;
+	    }
+	    HTAlert(*cp4 ? cp4 : anchor->charset);
+	}
+	FREE(cp3);
+    } else if (cp1 != NULL) {
+	/*
+	 * No charset parameter is present.  Ignore all other parameters, as we
+	 * do when charset is present.  - FM
+	 */
+	*cp1 = '\0';
+	format = HTAtom_for(cp);
+    }
+    FREE(cp);
+
+    /*
+     * Set up defaults, if needed.  - FM
+     */
+    if (!chartrans_ok && !anchor->charset && default_LYhndl >= 0) {
+	HTAnchor_setUCInfoStage(anchor, default_LYhndl,
+				UCT_STAGE_MIME,
+				UCT_SETBY_DEFAULT);
+    }
+    HTAnchor_copyUCInfoStage(anchor,
+			     UCT_STAGE_PARSER,
+			     UCT_STAGE_MIME,
+			     -1);
+
+    return format;
+}
+
+/*	Get various pieces of meta info from file name.
+ *	-----------------------------------------------
+ *
+ *  LYGetFileInfo fills in information that can be determined without
+ *  an actual (new) access to the filesystem, based on current suffix
+ *  and character set configuration.  If the file has been loaded and
+ *  parsed before  (with the same URL generated here!) and the anchor
+ *  is still around, some results may be influenced by that (in
+ *  particular, charset info from a META tag - this is not actually
+ *  tested!).
+ *  The caller should not keep pointers to the returned objects around
+ *  for too long, the valid lifetimes vary. In particular, the returned
+ *  charset string should be copied if necessary.  If return of the
+ *  file_anchor is requested, that one can be used to retrieve
+ *  additional bits of info that are stored in the anchor object and
+ *  are not covered here; as usual, don't keep pointers to the
+ *  file_anchor longer than necessary since the object may disappear
+ *  through HTuncache_current_document or at the next document load.
+ *  - kw
+ */
+void LYGetFileInfo(const char *filename,
+		   HTParentAnchor **pfile_anchor,
+		   HTFormat *pformat,
+		   HTAtom **pencoding,
+		   const char **pdesc,
+		   const char **pcharset,
+		   int *pfile_cs)
+{
+    char *Afn;
+    char *Aname = NULL;
+    HTFormat format;
+    HTAtom *myEnc = NULL;
+    HTParentAnchor *file_anchor;
+    const char *file_csname;
+    int file_cs;
+
+    /*
+     * Convert filename to URL.  Note that it is always supposed to be a
+     * filename, not maybe-filename-maybe-URL, so we don't use
+     * LYFillLocalFileURL and LYEnsureAbsoluteURL.  - kw
+     */
+    Afn = HTEscape(filename, URL_PATH);
+    LYLocalFileToURL(&Aname, Afn);
+    file_anchor = HTAnchor_findSimpleAddress(Aname);
+
+    file_csname = file_anchor->charset;
+    format = HTFileFormat(filename, &myEnc, pdesc);
+    format = HTCharsetFormat(format, file_anchor, UCLYhndl_HTFile_for_unspec);
+    file_cs = HTAnchor_getUCLYhndl(file_anchor, UCT_STAGE_MIME);
+    if (!file_csname) {
+	if (file_cs >= 0)
+	    file_csname = LYCharSet_UC[file_cs].MIMEname;
+	else
+	    file_csname = "display character set";
+    }
+    CTRACE((tfp, "GetFileInfo: '%s' is a%s %s %s file, charset=%s (%d).\n",
+	    filename,
+	    ((myEnc && *HTAtom_name(myEnc) == '8') ? "n" : myEnc ? "" :
+	     *HTAtom_name(format) == 'a' ? "n" : ""),
+	    myEnc ? HTAtom_name(myEnc) : "",
+	    HTAtom_name(format),
+	    file_csname,
+	    file_cs));
+    FREE(Afn);
+    FREE(Aname);
+    if (pfile_anchor)
+	*pfile_anchor = file_anchor;
+    if (pformat)
+	*pformat = format;
+    if (pencoding)
+	*pencoding = myEnc;
+    if (pcharset)
+	*pcharset = file_csname;
+    if (pfile_cs)
+	*pfile_cs = file_cs;
+}
+
+/*	Determine value from file name.
+ *	-------------------------------
+ *
+ */
+float HTFileValue(const char *filename)
+{
+    HTSuffix *suff;
+    int n;
+    int i;
+    int lf = (int) strlen(filename);
+
+#ifndef NO_INIT
+    if (!HTSuffixes)
+	HTFileInit();
+#endif /* !NO_INIT */
+    n = HTList_count(HTSuffixes);
+    for (i = 0; i < n; i++) {
+	int ls;
+
+	suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i);
+	ls = (int) strlen(suff->suffix);
+	if ((ls <= lf) && 0 == strcmp(suff->suffix, filename + lf - ls)) {
+	    CTRACE((tfp, "File: Value of %s is %.3f\n",
+		    filename, suff->quality));
+	    return suff->quality;	/* OK -- found */
+	}
+    }
+    return (float) 0.3;		/* Dunno! */
+}
+
+/*
+ *  Determine compression type from file name, by looking at its suffix.
+ *  Sets as side-effect a pointer to the "dot" that begins the suffix.
+ */
+CompressFileType HTCompressFileType(const char *filename,
+				    const char *dots,
+				    int *rootlen)
+{
+    CompressFileType result = cftNone;
+    size_t len = strlen(filename);
+    const char *ftype = filename + len;
+
+    VMS_DEL_VERSION(filename);
+
+    if ((len > 4)
+	&& !strcasecomp((ftype - 3), "bz2")
+	&& strchr(dots, ftype[-4]) != 0) {
+	result = cftBzip2;
+	ftype -= 4;
+    } else if ((len > 3)
+	       && !strcasecomp((ftype - 2), "gz")
+	       && strchr(dots, ftype[-3]) != 0) {
+	result = cftGzip;
+	ftype -= 3;
+    } else if ((len > 3)
+	       && !strcasecomp((ftype - 2), "zz")
+	       && strchr(dots, ftype[-3]) != 0) {
+	result = cftDeflate;
+	ftype -= 3;
+    } else if ((len > 2)
+	       && !strcmp((ftype - 1), "Z")
+	       && strchr(dots, ftype[-2]) != 0) {
+	result = cftCompress;
+	ftype -= 2;
+    }
+
+    *rootlen = (int) (ftype - filename);
+
+    CTRACE((tfp, "HTCompressFileType(%s) returns %d:%s\n",
+	    filename, (int) result, filename + *rootlen));
+    return result;
+}
+
+/*
+ *  Determine expected file-suffix from the compression method.
+ */
+const char *HTCompressTypeToSuffix(CompressFileType method)
+{
+    const char *result = "";
+
+    switch (method) {
+    default:
+    case cftNone:
+	result = "";
+	break;
+    case cftGzip:
+	result = ".gz";
+	break;
+    case cftCompress:
+	result = ".Z";
+	break;
+    case cftBzip2:
+	result = ".bz2";
+	break;
+    case cftDeflate:
+	result = ".zz";
+	break;
+    }
+    return result;
+}
+
+/*
+ *  Determine compression encoding from the compression method.
+ */
+const char *HTCompressTypeToEncoding(CompressFileType method)
+{
+    const char *result = NULL;
+
+    switch (method) {
+    default:
+    case cftNone:
+	result = NULL;
+	break;
+    case cftGzip:
+	result = "gzip";
+	break;
+    case cftCompress:
+	result = "compress";
+	break;
+    case cftBzip2:
+	result = "bzip2";
+	break;
+    case cftDeflate:
+	result = "deflate";
+	break;
+    }
+    return result;
+}
+
+/*
+ * Check if the token from "Content-Encoding" corresponds to a compression
+ * type.  RFC 2068 (and cut/paste into RFC 2616) lists these:
+ *	gzip
+ *	compress
+ *	deflate
+ * as well as "identity" (but that does nothing).
+ */
+CompressFileType HTEncodingToCompressType(const char *coding)
+{
+    CompressFileType result = cftNone;
+
+    if (coding == NULL) {
+	result = cftNone;
+    } else if (!strcasecomp(coding, "gzip") ||
+	       !strcasecomp(coding, "x-gzip")) {
+	result = cftGzip;
+    } else if (!strcasecomp(coding, "compress") ||
+	       !strcasecomp(coding, "x-compress")) {
+	result = cftCompress;
+    } else if (!strcasecomp(coding, "bzip2") ||
+	       !strcasecomp(coding, "x-bzip2")) {
+	result = cftBzip2;
+    } else if (!strcasecomp(coding, "deflate") ||
+	       !strcasecomp(coding, "x-deflate")) {
+	result = cftDeflate;
+    }
+    return result;
+}
+
+CompressFileType HTContentTypeToCompressType(const char *ct)
+{
+    CompressFileType method = cftNone;
+
+    if (ct == NULL) {
+	method = cftNone;
+    } else if (!strncasecomp(ct, "application/gzip", 16) ||
+	       !strncasecomp(ct, "application/x-gzip", 18)) {
+	method = cftGzip;
+    } else if (!strncasecomp(ct, "application/compress", 20) ||
+	       !strncasecomp(ct, "application/x-compress", 22)) {
+	method = cftCompress;
+    } else if (!strncasecomp(ct, "application/bzip2", 17) ||
+	       !strncasecomp(ct, "application/x-bzip2", 19)) {
+	method = cftBzip2;
+    }
+    return method;
+}
+
+/*
+ * Check the anchor's content_type and content_encoding elements for a gzip or
+ * Unix compressed file -FM, TD
+ */
+CompressFileType HTContentToCompressType(HTParentAnchor *anchor)
+{
+    CompressFileType method = cftNone;
+    const char *ct = HTAnchor_content_type(anchor);
+    const char *ce = HTAnchor_content_encoding(anchor);
+
+    if (ce == NULL && ct != 0) {
+	method = HTContentTypeToCompressType(ct);
+    } else if (ce != 0) {
+	method = HTEncodingToCompressType(ce);
+    }
+    return method;
+}
+
+/*	Determine write access to a file.
+ *	---------------------------------
+ *
+ *  On exit:
+ *	Returns YES if file can be accessed and can be written to.
+ *
+ *  Bugs:
+ *	1.	No code for non-unix systems.
+ *	2.	Isn't there a quicker way?
+ */
+BOOL HTEditable(const char *filename GCC_UNUSED)
+{
+#ifndef NO_GROUPS
+    GETGROUPS_T groups[NGROUPS];
+    uid_t myUid;
+    int ngroups;		/* The number of groups  */
+    struct stat fileStatus;
+    int i;
+
+    if (stat(filename, &fileStatus))	/* Get details of filename */
+	return NO;		/* Can't even access file! */
+
+    ngroups = getgroups(NGROUPS, groups);	/* Groups to which I belong  */
+    myUid = geteuid();		/* Get my user identifier */
+
+    if (TRACE) {
+	int i2;
+
+	fprintf(tfp,
+		"File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (",
+		(unsigned int) fileStatus.st_mode,
+		(int) fileStatus.st_uid,
+		(int) fileStatus.st_gid,
+		(int) myUid,
+		(int) ngroups);
+	for (i2 = 0; i2 < ngroups; i2++)
+	    fprintf(tfp, " %d", (int) groups[i2]);
+	fprintf(tfp, ")\n");
+    }
+
+    if (fileStatus.st_mode & 0002)	/* I can write anyway? */
+	return YES;
+
+    if ((fileStatus.st_mode & 0200)	/* I can write my own file? */
+	&&(fileStatus.st_uid == myUid))
+	return YES;
+
+    if (fileStatus.st_mode & 0020)	/* Group I am in can write? */
+    {
+	for (i = 0; i < ngroups; i++) {
+	    if (groups[i] == fileStatus.st_gid)
+		return YES;
+	}
+    }
+    CTRACE((tfp, "\tFile is not editable.\n"));
+#endif /* NO_GROUPS */
+    return NO;			/* If no excuse, can't do */
+}
+
+/*	Make a save stream.
+ *	-------------------
+ *
+ *	The stream must be used for writing back the file.
+ *	@@@ no backup done
+ */
+HTStream *HTFileSaveStream(HTParentAnchor *anchor)
+{
+    const char *addr = anchor->address;
+    char *localname = HTLocalName(addr);
+    FILE *fp = fopen(localname, BIN_W);
+
+    FREE(localname);
+    if (!fp)
+	return NULL;
+
+    return HTFWriter_new(fp);
+}
+
+/*	Output one directory entry.
+ *	---------------------------
+ */
+void HTDirEntry(HTStructured * target, const char *tail,
+		const char *entry)
+{
+    char *relative = NULL;
+    char *stripped = NULL;
+    char *escaped = NULL;
+    int len;
+
+    StrAllocCopy(escaped, entry);
+    LYTrimPathSep(escaped);
+    if (strcmp(escaped, "..") != 0) {
+	stripped = escaped;
+	escaped = HTEscape(stripped, URL_XPALPHAS);
+	if (((len = (int) strlen(escaped)) > 2) &&
+	    escaped[(len - 3)] == '%' &&
+	    escaped[(len - 2)] == '2' &&
+	    TOUPPER(escaped[(len - 1)]) == 'F') {
+	    escaped[(len - 3)] = '\0';
+	}
+    }
+
+    if (isEmpty(tail)) {
+	/*
+	 * Handle extra slash at end of path.
+	 */
+	HTStartAnchor(target, NULL, (escaped[0] != '\0' ? escaped : "/"));
+    } else {
+	/*
+	 * If empty tail, gives absolute ref below.
+	 */
+	relative = 0;
+	HTSprintf0(&relative, "%s%s%s",
+		   tail,
+		   (*escaped != '\0' ? "/" : ""),
+		   escaped);
+	HTStartAnchor(target, NULL, relative);
+	FREE(relative);
+    }
+    FREE(stripped);
+    FREE(escaped);
+}
+
+static BOOL view_structured(HTFormat format_out)
+{
+    BOOL result = FALSE;
+
+#ifdef USE_PRETTYSRC
+    if (psrc_view
+	|| (format_out == HTAtom_for("www/dump")))
+	result = TRUE;
+#else
+    if (format_out == WWW_SOURCE)
+	result = TRUE;
+#endif
+    return result;
+}
+
+/*
+ * Write a DOCTYPE to the given stream if we happen to want to see the
+ * source view, or are dumping source.  This is not needed when the source
+ * is not visible, since the document is rendered from a HTStructured object.
+ */
+void HTStructured_doctype(HTStructured * target, HTFormat format_out)
+{
+    if (view_structured(format_out))
+	PUTS("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
+}
+
+void HTStructured_meta(HTStructured * target, HTFormat format_out)
+{
+    if (view_structured(format_out))
+	PUTS("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n");
+}
+/*	Output parent directory entry.
+ *	------------------------------
+ *
+ *    This gives the TITLE and H1 header, and also a link
+ *    to the parent directory if appropriate.
+ *
+ *  On exit:
+ *	Returns TRUE if an "Up to <parent>" link was not created
+ *	for a readable local directory because LONG_LIST is defined
+ *	and NO_PARENT_DIR_REFERENCE is not defined, so that the
+ *	calling function should use LYListFmtParse() to create a link
+ *	to the parent directory.  Otherwise, it returns FALSE. - FM
+ */
+BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor,
+		 HTFormat format_out,
+		 int tildeIsTop)
+{
+    const char *logical = anchor->address;
+    char *path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION);
+    char *current;
+    char *cp = NULL;
+    BOOL need_parent_link = FALSE;
+    int i;
+
+#if defined(USE_DOS_DRIVES)
+    BOOL local_link = (strlen(logical) > 18
+		       && !strncasecomp(logical, "file://localhost/", 17)
+		       && LYIsDosDrive(logical + 17));
+    BOOL is_remote = !local_link;
+
+#else
+#define is_remote TRUE
+#endif
+
+    /*
+     * Check tildeIsTop for treating home directory as Welcome (assume the
+     * tilde is not followed by a username).  - FM
+     */
+    if (tildeIsTop && !StrNCmp(path, "/~", 2)) {
+	if (path[2] == '\0') {
+	    path[1] = '\0';
+	} else {
+	    for (i = 0; path[(i + 2)]; i++) {
+		path[i] = path[(i + 2)];
+	    }
+	    path[i] = '\0';
+	}
+    }
+
+    /*
+     * Trim out the ;type= parameter, if present.  - FM
+     */
+    if ((cp = strrchr(path, ';')) != NULL) {
+	if (!strncasecomp((cp + 1), "type=", 5)) {
+	    if (TOUPPER(*(cp + 6)) == 'D' ||
+		TOUPPER(*(cp + 6)) == 'A' ||
+		TOUPPER(*(cp + 6)) == 'I')
+		*cp = '\0';
+	}
+	cp = NULL;
+    }
+    current = LYPathLeaf(path);	/* last part or "" */
+
+    {
+	char *printable = NULL;
+
+#ifdef DIRED_SUPPORT
+	printable = HTURLPath_toFile(((!strncasecomp(path, "/%2F", 4))	/* "//" ? */
+				      ? (path + 1)
+				      : path),
+				     TRUE,
+				     is_remote);
+	if (0 == strncasecomp(printable, "/vmsysu:", 8) ||
+	    0 == strncasecomp(printable, "/anonymou.", 10)) {
+	    StrAllocCopy(cp, (printable + 1));
+	    StrAllocCopy(printable, cp);
+	    FREE(cp);
+	}
+#else
+	StrAllocCopy(printable, current);
+	HTUnEscape(printable);
+#endif /* DIRED_SUPPORT */
+
+	HTStructured_doctype(target, format_out);
+
+	START(HTML_HEAD);
+	PUTC('\n');
+	START(HTML_TITLE);
+	PUTS(*printable ? printable : WELCOME_MSG);
+	PUTS(SEGMENT_DIRECTORY);
+	END(HTML_TITLE);
+	PUTC('\n');
+	HTStructured_meta(target, format_out);
+	END(HTML_HEAD);
+	PUTC('\n');
+
+	START(HTML_BODY);
+	PUTC('\n');
+
+#ifdef DIRED_SUPPORT
+	START(HTML_H2);
+	PUTS(*printable ? SEGMENT_CURRENT_DIR : "");
+	PUTS(*printable ? printable : WELCOME_MSG);
+	END(HTML_H2);
+	PUTC('\n');
+#else
+	START(HTML_H1);
+	PUTS(*printable ? printable : WELCOME_MSG);
+	END(HTML_H1);
+	PUTC('\n');
+#endif /* DIRED_SUPPORT */
+	if (((0 == strncasecomp(printable, "vmsysu:", 7)) &&
+	     (cp = strchr(printable, '.')) != NULL &&
+	     strchr(cp, '/') == NULL) ||
+	    (0 == strncasecomp(printable, "anonymou.", 9) &&
+	     strchr(printable, '/') == NULL)) {
+	    FREE(printable);
+	    FREE(path);
+	    return (need_parent_link);
+	}
+	FREE(printable);
+    }
+
+#ifndef NO_PARENT_DIR_REFERENCE
+    /*
+     * Make link back to parent directory.
+     */
+    if (current - path > 0
+	&& LYIsPathSep(current[-1])
+	&& current[0] != '\0') {	/* was a slash AND something else too */
+	char *parent = NULL;
+	char *relative = NULL;
+
+	current[-1] = '\0';
+	parent = strrchr(path, '/');	/* penultimate slash */
+
+	if ((parent &&
+	     (!strcmp(parent, "/..") ||
+	      !strncasecomp(parent, "/%2F", 4))) ||
+	    !strncasecomp(current, "%2F", 3)) {
+	    FREE(path);
+	    return (need_parent_link);
+	}
+
+	relative = 0;
+	HTSprintf0(&relative, "%s/..", current);
+
+#if defined(DOSPATH) || defined(__EMX__)
+	if (local_link) {
+	    if (parent != 0 && strlen(parent) == 3) {
+		StrAllocCat(relative, "/.");
+	    }
+	} else
+#endif
+
+#if !defined (VMS)
+	{
+	    /*
+	     * On Unix, if it's not ftp and the directory cannot be read, don't
+	     * put out a link.
+	     *
+	     * On VMS, this problem is dealt with internally by
+	     * HTVMSBrowseDir().
+	     */
+	    DIR *dp = NULL;
+
+	    if (LYisLocalFile(logical)) {
+		/*
+		 * We need an absolute file path for the opendir.  We also need
+		 * to unescape for this test.  Don't worry about %2F now, they
+		 * presumably have been dealt with above, and shouldn't appear
+		 * for local files anyway...  Assume OS / filesystem will just
+		 * ignore superfluous slashes.  - KW
+		 */
+		char *fullparentpath = NULL;
+
+		/*
+		 * Path has been shortened above.
+		 */
+		StrAllocCopy(fullparentpath, *path ? path : "/");
+
+		/*
+		 * Guard against weirdness.
+		 */
+		if (0 == strcmp(current, "..")) {
+		    StrAllocCat(fullparentpath, "/../..");
+		} else if (0 == strcmp(current, ".")) {
+		    StrAllocCat(fullparentpath, "/..");
+		}
+
+		HTUnEscape(fullparentpath);
+		if ((dp = opendir(fullparentpath)) == NULL) {
+		    FREE(fullparentpath);
+		    FREE(relative);
+		    FREE(path);
+		    return (need_parent_link);
+		}
+		closedir(dp);
+		FREE(fullparentpath);
+#ifdef LONG_LIST
+		need_parent_link = TRUE;
+		FREE(path);
+		FREE(relative);
+		return (need_parent_link);
+#endif /* LONG_LIST */
+	    }
+	}
+#endif /* !VMS */
+	HTStartAnchor(target, "", relative);
+	FREE(relative);
+
+	PUTS(SEGMENT_UP_TO);
+	if (parent) {
+	    if ((0 == strcmp(current, ".")) ||
+		(0 == strcmp(current, ".."))) {
+		/*
+		 * Should not happen, but if it does, at least avoid giving
+		 * misleading info.  - KW
+		 */
+		PUTS("..");
+	    } else {
+		char *printable = NULL;
+
+		StrAllocCopy(printable, parent + 1);
+		HTUnEscape(printable);
+		PUTS(printable);
+		FREE(printable);
+	    }
+	} else {
+	    PUTC('/');
+	}
+	END(HTML_A);
+	PUTC('\n');
+    }
+#endif /* !NO_PARENT_DIR_REFERENCE */
+
+    FREE(path);
+    return (need_parent_link);
+}
+
+#if defined HAVE_READDIR
+/*	Send README file.
+ *	-----------------
+ *
+ *  If a README file exists, then it is inserted into the document here.
+ */
+static void do_readme(HTStructured * target, const char *localname)
+{
+    FILE *fp;
+    char *readme_file_name = NULL;
+    int ch;
+
+    HTSprintf0(&readme_file_name, "%s/%s", localname, HT_DIR_README_FILE);
+
+    fp = fopen(readme_file_name, "r");
+
+    if (fp) {
+	START(HTML_PRE);
+	while ((ch = fgetc(fp)) != EOF) {
+	    PUTC((char) ch);
+	}
+	END(HTML_PRE);
+	HTDisplayPartial();
+	fclose(fp);
+    }
+    FREE(readme_file_name);
+}
+
+#define DIRED_BLOK(obj) (((DIRED *)(obj))->sort_tags)
+#define DIRED_NAME(obj) (((DIRED *)(obj))->file_name)
+
+#define NM_cmp(a,b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0))
+
+#if defined(LONG_LIST) && defined(DIRED_SUPPORT)
+static const char *file_type(const char *path)
+{
+    const char *type;
+
+    while (*path == '.')
+	++path;
+    type = strchr(path, '.');
+    if (type == NULL)
+	type = "";
+    return type;
+}
+#endif /* LONG_LIST && DIRED_SUPPORT */
+
+static int dired_cmp(void *a, void *b)
+{
+    DIRED *p = (DIRED *) a;
+    DIRED *q = (DIRED *) b;
+    int code = p->sort_tags - q->sort_tags;
+
+#if defined(LONG_LIST) && defined(DIRED_SUPPORT)
+    if (code == 0) {
+	switch (dir_list_order) {
+	case ORDER_BY_SIZE:
+	    code = -NM_cmp(p->file_info.st_size, q->file_info.st_size);
+	    break;
+	case ORDER_BY_DATE:
+	    code = -NM_cmp(p->file_info.st_mtime, q->file_info.st_mtime);
+	    break;
+	case ORDER_BY_MODE:
+	    code = NM_cmp(p->file_info.st_mode, q->file_info.st_mode);
+	    break;
+	case ORDER_BY_USER:
+	    code = NM_cmp(p->file_info.st_uid, q->file_info.st_uid);
+	    break;
+	case ORDER_BY_GROUP:
+	    code = NM_cmp(p->file_info.st_gid, q->file_info.st_gid);
+	    break;
+	case ORDER_BY_TYPE:
+	    code = AS_cmp(file_type(p->file_name), file_type(q->file_name));
+	    break;
+	default:
+	    code = 0;
+	    break;
+	}
+    }
+#endif /* LONG_LIST && DIRED_SUPPORT */
+    if (code == 0)
+	code = AS_cmp(p->file_name, q->file_name);
+#if 0
+    CTRACE((tfp, "dired_cmp(%d) ->%d\n\t%c:%s (%s)\n\t%c:%s (%s)\n",
+	    dir_list_order,
+	    code,
+	    p->sort_tags, p->file_name, file_type(p->file_name),
+	    q->sort_tags, q->file_name, file_type(q->file_name)));
+#endif
+    return code;
+}
+
+static int print_local_dir(DIR *dp, char *localname,
+			   HTParentAnchor *anchor,
+			   HTFormat format_out,
+			   HTStream *sink)
+{
+    HTStructured *target;	/* HTML object */
+    HTBTree *bt;
+    HTStructuredClass targetClass;
+    STRUCT_DIRENT *dirbuf;
+    char *pathname = NULL;
+    char *tail = NULL;
+    const char *p;
+    char *tmpfilename = NULL;
+    BOOL need_parent_link = FALSE;
+    BOOL preformatted = FALSE;
+    int status;
+    struct stat *actual_info;
+
+#ifdef DISP_PARTIAL
+    int num_of_entries = 0;	/* lines counter */
+#endif
+
+#ifdef S_IFLNK
+    struct stat link_info;
+#endif
+
+    CTRACE((tfp, "print_local_dir() started\n"));
+
+    pathname = HTParse(anchor->address, "",
+		       PARSE_PATH + PARSE_PUNCTUATION);
+
+    if ((p = strrchr(pathname, '/')) == NULL)
+	p = "/";
+    StrAllocCopy(tail, (p + 1));
+    FREE(pathname);
+
+    if (UCLYhndl_HTFile_for_unspec >= 0) {
+	HTAnchor_setUCInfoStage(anchor,
+				UCLYhndl_HTFile_for_unspec,
+				UCT_STAGE_PARSER,
+				UCT_SETBY_DEFAULT);
+    }
+
+    target = HTML_new(anchor, format_out, sink);
+    targetClass = *target->isa;	/* Copy routine entry points */
+
+    /*
+     * The need_parent_link flag will be set if an "Up to <parent>" link was
+     * not created for a readable parent in HTDirTitles() because LONG_LIST is
+     * defined and NO_PARENT_DIR_REFERENCE is not defined so that need we to
+     * create the link via an LYListFmtParse() call.  - FM
+     */
+    need_parent_link = HTDirTitles(target, anchor, format_out, FALSE);
+
+#ifdef DIRED_SUPPORT
+    if (!isLYNXCGI(anchor->address)) {
+	HTAnchor_setFormat(anchor, WWW_DIRED);
+	lynx_edit_mode = TRUE;
+    }
+#endif /* DIRED_SUPPORT */
+    if (HTDirReadme == HT_DIR_README_TOP)
+	do_readme(target, localname);
+
+    bt = HTBTree_new(dired_cmp);
+
+    _HTProgress(READING_DIRECTORY);
+    status = HT_LOADED;		/* assume we don't get interrupted */
+    while ((dirbuf = readdir(dp)) != NULL) {
+	/*
+	 * While there are directory entries to be read...
+	 */
+	DIRED *data = NULL;
+
+#ifdef STRUCT_DIRENT__D_INO
+	if (dirbuf->d_ino == 0)
+	    /*
+	     * If the entry is not being used, skip it.
+	     */
+	    continue;
+#endif
+	/*
+	 * Skip self, parent if handled in HTDirTitles() or if
+	 * NO_PARENT_DIR_REFERENCE is not defined, and any dot files if
+	 * no_dotfiles is set or show_dotfiles is not set.  - FM
+	 */
+	if (!strcmp(dirbuf->d_name, ".") /* self       */ ||
+	    (!strcmp(dirbuf->d_name, "..") /* parent */ &&
+	     need_parent_link == FALSE) ||
+	    ((strcmp(dirbuf->d_name, "..")) &&
+	     (dirbuf->d_name[0] == '.' &&
+	      (no_dotfiles || !show_dotfiles))))
+	    continue;
+
+	StrAllocCopy(tmpfilename, localname);
+	/*
+	 * If filename is not root directory, add trailing separator.
+	 */
+	LYAddPathSep(&tmpfilename);
+
+	StrAllocCat(tmpfilename, dirbuf->d_name);
+	data = (DIRED *) malloc(sizeof(DIRED) + strlen(dirbuf->d_name) + 4);
+	if (data == NULL) {
+	    status = HT_PARTIAL_CONTENT;
+	    break;
+	}
+	LYTrimPathSep(tmpfilename);
+
+	actual_info = &(data->file_info);
+#ifdef S_IFLNK
+	if (lstat(tmpfilename, actual_info) < 0) {
+	    actual_info->st_mode = 0;
+	} else {
+	    if (S_ISLNK(actual_info->st_mode)) {
+		actual_info = &link_info;
+		if (stat(tmpfilename, actual_info) < 0)
+		    actual_info->st_mode = 0;
+	    }
+	}
+#else
+	if (stat(tmpfilename, actual_info) < 0)
+	    actual_info->st_mode = 0;
+#endif
+
+	strcpy(data->file_name, dirbuf->d_name);
+#ifndef DIRED_SUPPORT
+	if (S_ISDIR(actual_info->st_mode)) {
+	    data->sort_tags = 'D';
+	} else {
+	    data->sort_tags = 'F';
+	    /* D & F to have first directories, then files */
+	}
+#else
+	if (S_ISDIR(actual_info->st_mode)) {
+	    if (dir_list_style == MIXED_STYLE) {
+		data->sort_tags = ' ';
+		LYAddPathSep0(data->file_name);
+	    } else if (!strcmp(dirbuf->d_name, "..")) {
+		data->sort_tags = 'A';
+	    } else {
+		data->sort_tags = 'D';
+	    }
+	} else if (dir_list_style == MIXED_STYLE) {
+	    data->sort_tags = ' ';
+	} else if (dir_list_style == FILES_FIRST) {
+	    data->sort_tags = 'C';
+	    /* C & D to have first files, then directories */
+	} else {
+	    data->sort_tags = 'F';
+	}
+#endif /* !DIRED_SUPPORT */
+	/*
+	 * Sort dirname in the tree bt.
+	 */
+	HTBTree_add(bt, data);
+
+#ifdef DISP_PARTIAL
+	/* optimize for expensive operation: */
+	if (num_of_entries % (partial_threshold > 0 ?
+			      partial_threshold : display_lines) == 0) {
+	    if (HTCheckForInterrupt()) {
+		status = HT_PARTIAL_CONTENT;
+		break;
+	    }
+	}
+	num_of_entries++;
+#endif /* DISP_PARTIAL */
+
+    }				/* end while directory entries left to read */
+
+    if (status != HT_PARTIAL_CONTENT)
+	_HTProgress(OPERATION_OK);
+    else
+	CTRACE((tfp, "Reading the directory interrupted by user\n"));
+
+    /*
+     * Run through tree printing out in order.
+     */
+    {
+	HTBTElement *next_element = HTBTree_next(bt, NULL);
+
+	/* pick up the first element of the list */
+	int num_of_entries_output = 0;	/* lines counter */
+
+	char state;
+
+	/* I for initial (.. file),
+	   D for directory file,
+	   F for file */
+
+#ifdef DIRED_SUPPORT
+	char test;
+#endif /* DIRED_SUPPORT */
+	state = 'I';
+
+	while (next_element != NULL) {
+	    DIRED *entry;
+
+#ifndef DISP_PARTIAL
+	    if (num_of_entries_output % HTMAX(display_lines, 10) == 0) {
+		if (HTCheckForInterrupt()) {
+		    _HTProgress(TRANSFER_INTERRUPTED);
+		    status = HT_PARTIAL_CONTENT;
+		    break;
+		}
+	    }
+#endif
+	    StrAllocCopy(tmpfilename, localname);
+	    /*
+	     * If filename is not root directory.
+	     */
+	    LYAddPathSep(&tmpfilename);
+
+	    entry = (DIRED *) (HTBTree_object(next_element));
+	    /*
+	     * Append the current entry's filename to the path.
+	     */
+	    StrAllocCat(tmpfilename, entry->file_name);
+	    HTSimplify(tmpfilename);
+	    /*
+	     * Output the directory entry.
+	     */
+	    if (strcmp(DIRED_NAME(HTBTree_object(next_element)), "..")) {
+#ifdef DIRED_SUPPORT
+		test =
+		    (char) (DIRED_BLOK(HTBTree_object(next_element))
+			    == 'D' ? 'D' : 'F');
+		if (state != test) {
+#ifndef LONG_LIST
+		    if (dir_list_style == FILES_FIRST) {
+			if (state == 'F') {
+			    END(HTML_DIR);
+			    PUTC('\n');
+			}
+		    } else if (dir_list_style != MIXED_STYLE)
+			if (state == 'D') {
+			    END(HTML_DIR);
+			    PUTC('\n');
+			}
+#endif /* !LONG_LIST */
+		    state =
+			(char) (DIRED_BLOK(HTBTree_object(next_element))
+				== 'D' ? 'D' : 'F');
+		    if (preformatted) {
+			END(HTML_PRE);
+			PUTC('\n');
+			preformatted = FALSE;
+		    }
+		    START(HTML_H2);
+		    if (dir_list_style != MIXED_STYLE) {
+			START(HTML_EM);
+			PUTS(state == 'D'
+			     ? LABEL_SUBDIRECTORIES
+			     : LABEL_FILES);
+			END(HTML_EM);
+		    }
+		    END(HTML_H2);
+		    PUTC('\n');
+#ifndef LONG_LIST
+		    START(HTML_DIR);
+		    PUTC('\n');
+#endif /* !LONG_LIST */
+		}
+#else
+		if (state != DIRED_BLOK(HTBTree_object(next_element))) {
+#ifndef LONG_LIST
+		    if (state == 'D') {
+			END(HTML_DIR);
+			PUTC('\n');
+		    }
+#endif /* !LONG_LIST */
+		    state =
+			(char) (DIRED_BLOK(HTBTree_object(next_element))
+				== 'D' ? 'D' : 'F');
+		    if (preformatted) {
+			END(HTML_PRE);
+			PUTC('\n');
+			preformatted = FALSE;
+		    }
+		    START(HTML_H2);
+		    START(HTML_EM);
+		    PUTS(state == 'D'
+			 ? LABEL_SUBDIRECTORIES
+			 : LABEL_FILES);
+		    END(HTML_EM);
+		    END(HTML_H2);
+		    PUTC('\n');
+#ifndef LONG_LIST
+		    START(HTML_DIR);
+		    PUTC('\n');
+#endif /* !LONG_LIST */
+		}
+#endif /* DIRED_SUPPORT */
+#ifndef LONG_LIST
+		START(HTML_LI);
+#endif /* !LONG_LIST */
+	    }
+	    if (!preformatted) {
+		START(HTML_PRE);
+		PUTC('\n');
+		preformatted = TRUE;
+	    }
+#ifdef LONG_LIST
+	    LYListFmtParse(list_format, entry, tmpfilename, target, tail);
+#else
+	    HTDirEntry(target, tail, entry->file_name);
+	    PUTS(entry->file_name);
+	    END(HTML_A);
+	    MAYBE_END(HTML_LI);
+	    PUTC('\n');
+#endif /* LONG_LIST */
+
+	    next_element = HTBTree_next(bt, next_element);
+	    /* pick up the next element of the list;
+	       if none, return NULL */
+
+	    /* optimize for expensive operation: */
+#ifdef DISP_PARTIAL
+	    if (num_of_entries_output %
+		((partial_threshold > 0)
+		 ? partial_threshold
+		 : display_lines) == 0) {
+		/* num_of_entries, num_of_entries_output... */
+		HTDisplayPartial();
+
+		if (HTCheckForInterrupt()) {
+		    _HTProgress(TRANSFER_INTERRUPTED);
+		    status = HT_PARTIAL_CONTENT;
+		    break;
+		}
+	    }
+	    num_of_entries_output++;
+#endif /* DISP_PARTIAL */
+
+	}			/* end while next_element */
+
+	if (status == HT_LOADED) {
+	    if (state == 'I') {
+		START(HTML_P);
+		PUTS("Empty Directory");
+	    }
+#ifndef LONG_LIST
+	    else
+		END(HTML_DIR);
+#endif /* !LONG_LIST */
+	}
+    }				/* end printing out the tree in order */
+    if (preformatted) {
+	END(HTML_PRE);
+	PUTC('\n');
+    }
+    END(HTML_BODY);
+    PUTC('\n');
+
+    FREE(tmpfilename);
+    FREE(tail);
+    HTBTreeAndObject_free(bt);
+
+    if (status == HT_LOADED) {
+	if (HTDirReadme == HT_DIR_README_BOTTOM)
+	    do_readme(target, localname);
+	FREE_TARGET;
+    } else {
+	ABORT_TARGET;
+    }
+    HTFinishDisplayPartial();
+    return status;		/* document loaded, maybe partial */
+}
+#endif /* HAVE_READDIR */
+
+#ifndef VMS
+int HTStat(const char *filename,
+	   struct stat *data)
+{
+    int result = -1;
+    size_t len = strlen(filename);
+
+    if (len != 0 && LYIsPathSep(filename[len - 1])) {
+	char *temp_name = NULL;
+
+	HTSprintf0(&temp_name, "%s.", filename);
+	result = HTStat(temp_name, data);
+	FREE(temp_name);
+    } else {
+	result = stat(filename, data);
+#ifdef _WINDOWS
+	/*
+	 * Someone claims that stat() doesn't give the proper result for a
+	 * directory on Windows.
+	 */
+	if (result == -1
+	    && access(filename, 0) == 0) {
+	    data->st_mode = S_IFDIR;
+	    result = 0;
+	}
+#endif
+    }
+    return result;
+}
+#endif
+
+#if defined(USE_ZLIB) || defined(USE_BZLIB)
+static BOOL sniffStream(FILE *fp, char *buffer, size_t needed)
+{
+    long offset = ftell(fp);
+    BOOL result = FALSE;
+
+    if (fread(buffer, sizeof(char), needed, fp) == needed) {
+	result = TRUE;
+    }
+    if (fseek(fp, offset, SEEK_SET) < 0) {
+	CTRACE((tfp, "error seeking in stream\n"));
+	result = FALSE;
+    }
+    return result;
+}
+#endif
+
+#ifdef USE_ZLIB
+static BOOL isGzipStream(FILE *fp)
+{
+    char buffer[3];
+    BOOL result;
+
+    if (sniffStream(fp, buffer, sizeof(buffer))
+	&& !MemCmp(buffer, "\037\213", sizeof(buffer) - 1)) {
+	result = TRUE;
+    } else {
+	CTRACE((tfp, "not a gzip-stream\n"));
+	result = FALSE;
+    }
+    return result;
+}
+
+static BOOL isDeflateStream(FILE *fp)
+{
+    char buffer[3];
+    BOOL result;
+
+    if (sniffStream(fp, buffer, sizeof(buffer))
+	&& !MemCmp(buffer, "\170\234", sizeof(buffer) - 1)) {
+	result = TRUE;
+    } else {
+	CTRACE((tfp, "not a deflate-stream\n"));
+	result = FALSE;
+    }
+    return result;
+}
+#endif
+
+#ifdef USE_BZLIB
+static BOOL isBzip2Stream(FILE *fp)
+{
+    char buffer[6];
+    BOOL result;
+
+    if (sniffStream(fp, buffer, sizeof(buffer))
+	&& !MemCmp(buffer, "BZh", 3)
+	&& isdigit(UCH(buffer[3]))
+	&& isdigit(UCH(buffer[4]))) {
+	result = TRUE;
+    } else {
+	CTRACE((tfp, "not a bzip2-stream\n"));
+	result = FALSE;
+    }
+    return result;
+}
+#endif
+
+#ifdef VMS
+#define FOPEN_MODE(bin) "r", "shr=put", "shr=upd"
+#define DOT_STRING "._-"	/* FIXME: should we check if suffix is after ']' or ':' ? */
+#else
+#define FOPEN_MODE(bin) (bin ? BIN_R : "r")
+#define DOT_STRING "."
+#endif
+
+static int decompressAndParse(HTParentAnchor *anchor,
+			      HTFormat format_out,
+			      HTStream *sink,
+			      char *nodename GCC_UNUSED,
+			      char *filename,
+			      HTAtom *myEncoding,
+			      HTFormat format,
+			      int *statusp)
+{
+    HTAtom *encoding = 0;
+
+#ifdef USE_ZLIB
+    FILE *zzfp = 0;
+    gzFile gzfp = 0;
+#endif /* USE_ZLIB */
+#ifdef USE_BZLIB
+    BZFILE *bzfp = 0;
+#endif /* USE_ZLIB */
+#if defined(USE_ZLIB) || defined(USE_BZLIB)
+    CompressFileType internal_decompress = cftNone;
+    BOOL failed_decompress = NO;
+#endif
+    int rootlen = 0;
+    char *localname = filename;
+    int bin;
+    FILE *fp;
+
+#ifdef VMS
+    /*
+     * Assume that the file is in Unix-style syntax if it contains a '/' after
+     * the leading one.  @@
+     */
+    localname = (strchr(localname + 1, '/')
+		 ? HTVMS_name(nodename, localname)
+		 : localname + 1);
+#endif /* VMS */
+
+    bin = HTCompressFileType(filename, ".", &rootlen) != cftNone;
+    fp = fopen(localname, FOPEN_MODE(bin));
+
+#ifdef VMS
+    /*
+     * If the file wasn't VMS syntax, then perhaps it is Ultrix.
+     */
+    if (!fp) {
+	char *ultrixname = 0;
+
+	CTRACE((tfp, "HTLoadFile: Can't open as %s\n", localname));
+	HTSprintf0(&ultrixname, "%s::\"%s\"", nodename, filename);
+	fp = fopen(ultrixname, FOPEN_MODE(bin));
+	if (!fp) {
+	    CTRACE((tfp, "HTLoadFile: Can't open as %s\n", ultrixname));
+	}
+	FREE(ultrixname);
+    }
+#endif /* VMS */
+    CTRACE((tfp, "HTLoadFile: Opening `%s' gives %p\n", localname, (void *) fp));
+    if (fp) {			/* Good! */
+	if (HTEditable(localname)) {
+	    HTAtom *put = HTAtom_for("PUT");
+	    HTList *methods = HTAnchor_methods(anchor);
+
+	    if (HTList_indexOf(methods, put) == (-1)) {
+		HTList_addObject(methods, put);
+	    }
+	}
+	/*
+	 * Fake a Content-Encoding for compressed files.  - FM
+	 */
+	if (!IsUnityEnc(myEncoding)) {
+	    /*
+	     * We already know from the call to HTFileFormat that
+	     * this is a compressed file, no need to look at the filename
+	     * again.  - kw
+	     */
+#if defined(USE_ZLIB) || defined(USE_BZLIB)
+	    CompressFileType method = HTEncodingToCompressType(HTAtom_name(myEncoding));
+#endif
+
+#define isDOWNLOAD(m) (strcmp(format_out->name, "www/download") && (method == m))
+#ifdef USE_ZLIB
+	    if (isDOWNLOAD(cftGzip)) {
+		if (isGzipStream(fp)) {
+		    fclose(fp);
+		    gzfp = gzopen(localname, BIN_R);
+
+		    CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n",
+			    localname, gzfp));
+		}
+		internal_decompress = cftGzip;
+	    } else if (isDOWNLOAD(cftDeflate)) {
+		if (isDeflateStream(fp)) {
+		    zzfp = fp;
+		    fp = 0;
+
+		    CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n",
+			    localname, (void *) zzfp));
+		}
+		internal_decompress = cftDeflate;
+	    } else
+#endif /* USE_ZLIB */
+#ifdef USE_BZLIB
+	    if (isDOWNLOAD(cftBzip2)) {
+		if (isBzip2Stream(fp)) {
+		    fclose(fp);
+		    bzfp = BZ2_bzopen(localname, BIN_R);
+
+		    CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n",
+			    localname, bzfp));
+		}
+		internal_decompress = cftBzip2;
+	    } else
+#endif /* USE_BZLIB */
+	    {
+		StrAllocCopy(anchor->content_type, format->name);
+		StrAllocCopy(anchor->content_encoding, HTAtom_name(myEncoding));
+		format = HTAtom_for("www/compressed");
+	    }
+	} else {
+	    CompressFileType cft = HTCompressFileType(localname, DOT_STRING, &rootlen);
+
+	    if (cft != cftNone) {
+		char *cp = NULL;
+
+		StrAllocCopy(cp, localname);
+		cp[rootlen] = '\0';
+		format = HTFileFormat(cp, &encoding, NULL);
+		FREE(cp);
+		format = HTCharsetFormat(format, anchor,
+					 UCLYhndl_HTFile_for_unspec);
+		StrAllocCopy(anchor->content_type, format->name);
+	    }
+
+	    switch (cft) {
+	    case cftCompress:
+		StrAllocCopy(anchor->content_encoding, "x-compress");
+		format = HTAtom_for("www/compressed");
+		break;
+	    case cftDeflate:
+		StrAllocCopy(anchor->content_encoding, "x-deflate");
+#ifdef USE_ZLIB
+		if (strcmp(format_out->name, "www/download") != 0) {
+		    if (isDeflateStream(fp)) {
+			zzfp = fp;
+			fp = 0;
+
+			CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n",
+				localname, (void *) zzfp));
+		    }
+		    internal_decompress = cftDeflate;
+		}
+#else /* USE_ZLIB */
+		format = HTAtom_for("www/compressed");
+#endif /* USE_ZLIB */
+		break;
+	    case cftGzip:
+		StrAllocCopy(anchor->content_encoding, "x-gzip");
+#ifdef USE_ZLIB
+		if (strcmp(format_out->name, "www/download") != 0) {
+		    if (isGzipStream(fp)) {
+			fclose(fp);
+			gzfp = gzopen(localname, BIN_R);
+
+			CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n",
+				localname, gzfp));
+		    }
+		    internal_decompress = cftGzip;
+		}
+#else /* USE_ZLIB */
+		format = HTAtom_for("www/compressed");
+#endif /* USE_ZLIB */
+		break;
+	    case cftBzip2:
+		StrAllocCopy(anchor->content_encoding, "x-bzip2");
+#ifdef USE_BZLIB
+		if (strcmp(format_out->name, "www/download") != 0) {
+		    if (isBzip2Stream(fp)) {
+			fclose(fp);
+			bzfp = BZ2_bzopen(localname, BIN_R);
+
+			CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n",
+				localname, bzfp));
+		    }
+		    internal_decompress = cftBzip2;
+		}
+#else /* USE_BZLIB */
+		format = HTAtom_for("www/compressed");
+#endif /* USE_BZLIB */
+		break;
+	    case cftNone:
+		break;
+	    }
+	}
+#if defined(USE_ZLIB) || defined(USE_BZLIB)
+	if (internal_decompress != cftNone) {
+	    switch (internal_decompress) {
+#ifdef USE_ZLIB
+	    case cftDeflate:
+		failed_decompress = (BOOLEAN) (zzfp == NULL);
+		break;
+	    case cftCompress:
+	    case cftGzip:
+		failed_decompress = (BOOLEAN) (gzfp == NULL);
+		break;
+#endif
+#ifdef USE_BZLIB
+	    case cftBzip2:
+		failed_decompress = (BOOLEAN) (bzfp == NULL);
+		break;
+#endif
+	    default:
+		failed_decompress = YES;
+		break;
+	    }
+	    if (failed_decompress) {
+		*statusp = HTLoadError(NULL,
+				       -(HT_ERROR),
+				       FAILED_OPEN_COMPRESSED_FILE);
+	    } else {
+		char *sugfname = NULL;
+
+		if (anchor->SugFname) {
+		    StrAllocCopy(sugfname, anchor->SugFname);
+		} else {
+		    char *anchor_path = HTParse(anchor->address, "",
+						PARSE_PATH + PARSE_PUNCTUATION);
+		    char *lastslash;
+
+		    HTUnEscape(anchor_path);
+		    lastslash = strrchr(anchor_path, '/');
+		    if (lastslash)
+			StrAllocCopy(sugfname, lastslash + 1);
+		    FREE(anchor_path);
+		}
+		FREE(anchor->content_encoding);
+		if (sugfname && *sugfname)
+		    HTCheckFnameForCompression(&sugfname, anchor,
+					       TRUE);
+		if (sugfname && *sugfname)
+		    StrAllocCopy(anchor->SugFname, sugfname);
+		FREE(sugfname);
+#ifdef USE_BZLIB
+		if (bzfp)
+		    *statusp = HTParseBzFile(format, format_out,
+					     anchor,
+					     bzfp, sink);
+#endif
+#ifdef USE_ZLIB
+		if (gzfp)
+		    *statusp = HTParseGzFile(format, format_out,
+					     anchor,
+					     gzfp, sink);
+		else if (zzfp)
+		    *statusp = HTParseZzFile(format, format_out,
+					     anchor,
+					     zzfp, sink);
+#endif
+	    }
+	} else
+#endif /* USE_ZLIB || USE_BZLIB */
+	{
+	    *statusp = HTParseFile(format, format_out, anchor, fp, sink);
+	    fclose(fp);
+	}
+	return TRUE;
+    }				/* If successful open */
+    return FALSE;
+}
+
+/*	Load a document.
+ *	----------------
+ *
+ *  On entry:
+ *	addr		must point to the fully qualified hypertext reference.
+ *			This is the physical address of the file
+ *
+ *  On exit:
+ *	returns		<0		Error has occurred.
+ *			HTLOADED	OK
+ *
+ */
+int HTLoadFile(const char *addr,
+	       HTParentAnchor *anchor,
+	       HTFormat format_out,
+	       HTStream *sink)
+{
+    char *filename = NULL;
+    char *acc_method = NULL;
+    HTFormat format;
+    char *nodename = NULL;
+    char *newname = NULL;	/* Simplified name of file */
+    HTAtom *myEncoding = NULL;	/* enc of this file, may be gzip etc. */
+    int status = -1;
+
+#ifndef DISABLE_FTP
+    char *ftp_newhost;
+#endif
+
+#ifdef VMS
+    struct stat stat_info;
+#endif /* VMS */
+
+    /*
+     * Reduce the filename to a basic form (hopefully unique!).
+     */
+    StrAllocCopy(newname, addr);
+    filename = HTParse(newname, "", PARSE_PATH | PARSE_PUNCTUATION);
+    nodename = HTParse(newname, "", PARSE_HOST);
+
+    /*
+     * If access is ftp, or file is on another host, invoke ftp now.
+     */
+    acc_method = HTParse(newname, "", PARSE_ACCESS);
+    if (strcmp("ftp", acc_method) == 0 ||
+	(!LYSameHostname("localhost", nodename) &&
+	 !LYSameHostname(nodename, HTHostName()))) {
+	status = -1;
+	FREE(newname);
+	FREE(filename);
+	FREE(nodename);
+	FREE(acc_method);
+#ifndef DISABLE_FTP
+	ftp_newhost = HTParse(addr, "", PARSE_HOST);
+	if (strcmp(ftp_lasthost, ftp_newhost))
+	    ftp_local_passive = ftp_passive;
+
+	status = HTFTPLoad(addr, anchor, format_out, sink);
+
+	if (ftp_passive == ftp_local_passive) {
+	    if ((status >= 400) || (status < 0)) {
+		ftp_local_passive = (BOOLEAN) !ftp_passive;
+		status = HTFTPLoad(addr, anchor, format_out, sink);
+	    }
+	}
+
+	free(ftp_lasthost);
+	ftp_lasthost = ftp_newhost;
+#endif /* DISABLE_FTP */
+	return status;
+    } else {
+	FREE(newname);
+	FREE(acc_method);
+    }
+#if defined(VMS) || defined(USE_DOS_DRIVES)
+    HTUnEscape(filename);
+#endif /* VMS */
+
+    /*
+     * Determine the format and encoding mapped to any suffix.
+     */
+    if (anchor->content_type && anchor->content_encoding) {
+	/*
+	 * If content_type and content_encoding are BOTH already set in the
+	 * anchor object, we believe it and don't try to derive format and
+	 * encoding from the filename.  - kw
+	 */
+	format = HTAtom_for(anchor->content_type);
+	myEncoding = HTAtom_for(anchor->content_encoding);
+    } else {
+	int default_UCLYhndl = UCLYhndl_HTFile_for_unspec;
+
+	if (force_old_UCLYhndl_on_reload) {
+	    force_old_UCLYhndl_on_reload = FALSE;
+	    default_UCLYhndl = forced_UCLYhdnl;
+	}
+
+	format = HTFileFormat(filename, &myEncoding, NULL);
+
+	/*
+	 * Check the format for an extended MIME charset value, and act on it
+	 * if present.  Otherwise, assume what is indicated by the last
+	 * parameter (fallback will effectively be UCLYhndl_for_unspec, by
+	 * default ISO-8859-1).  - kw
+	 */
+	format = HTCharsetFormat(format, anchor, default_UCLYhndl);
+    }
+
+#ifdef VMS
+    /*
+     * Check to see if the 'filename' is in fact a directory.  If it is create
+     * a new hypertext object containing a list of files and subdirectories
+     * contained in the directory.  All of these are links to the directories
+     * or files listed.
+     */
+    if (HTStat(filename, &stat_info) == -1) {
+	CTRACE((tfp, "HTLoadFile: Can't stat %s\n", filename));
+    } else {
+	if (S_ISDIR(stat_info.st_mode)) {
+	    if (HTDirAccess == HT_DIR_FORBID) {
+		FREE(filename);
+		FREE(nodename);
+		return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN);
+	    }
+
+	    if (HTDirAccess == HT_DIR_SELECTIVE) {
+		char *enable_file_name = NULL;
+
+		HTSprintf0(&enable_file_name, "%s/%s", filename, HT_DIR_ENABLE_FILE);
+		if (HTStat(enable_file_name, &stat_info) == -1) {
+		    FREE(filename);
+		    FREE(nodename);
+		    FREE(enable_file_name);
+		    return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS);
+		}
+	    }
+
+	    FREE(filename);
+	    FREE(nodename);
+	    return HTVMSBrowseDir(addr, anchor, format_out, sink);
+	}
+    }
+
+    if (decompressAndParse(anchor,
+			   format_out,
+			   sink,
+			   nodename,
+			   filename,
+			   myEncoding,
+			   format,
+			   &status)) {
+	FREE(nodename);
+	FREE(filename);
+	return status;
+    }
+    FREE(filename);
+
+#else /* not VMS: */
+
+    FREE(filename);
+
+    /*
+     * For unix, we try to translate the name into the name of a transparently
+     * mounted file.
+     *
+     * Not allowed in secure (HTClientHost) situations.  TBL 921019
+     */
+#ifndef NO_UNIX_IO
+    /*  Need protection here for telnet server but not httpd server. */
+
+    if (!HTSecure) {		/* try local file system */
+	char *localname = HTLocalName(addr);
+	struct stat dir_info;
+
+#ifdef HAVE_READDIR
+	/*
+	 * Multiformat handling.
+	 *
+	 * If needed, scan directory to find a good file.  Bug:  We don't stat
+	 * the file to find the length.
+	 */
+	if ((strlen(localname) > strlen(MULTI_SUFFIX)) &&
+	    (0 == strcmp(localname + strlen(localname) - strlen(MULTI_SUFFIX),
+			 MULTI_SUFFIX))) {
+	    DIR *dp = 0;
+	    BOOL forget_multi = NO;
+
+	    STRUCT_DIRENT *dirbuf;
+	    float best = (float) NO_VALUE_FOUND;	/* So far best is bad */
+	    HTFormat best_rep = NULL;	/* Set when rep found */
+	    HTAtom *best_enc = NULL;
+	    char *best_name = NULL;	/* Best dir entry so far */
+
+	    char *base = strrchr(localname, '/');
+	    size_t baselen = 0;
+
+	    if (!base || base == localname) {
+		forget_multi = YES;
+	    } else {
+		*base++ = '\0';	/* Just got directory name */
+		baselen = strlen(base) - strlen(MULTI_SUFFIX);
+		base[baselen] = '\0';	/* Chop off suffix */
+
+		dp = opendir(localname);
+	    }
+	    if (forget_multi || !dp) {
+		FREE(localname);
+		FREE(nodename);
+		return HTLoadError(sink, 500, FAILED_DIR_SCAN);
+	    }
+
+	    while ((dirbuf = readdir(dp)) != NULL) {
+		/*
+		 * While there are directory entries to be read...
+		 */
+#ifdef STRUCT_DIRENT__D_INO
+		if (dirbuf->d_ino == 0)
+		    continue;	/* if the entry is not being used, skip it */
+#endif
+		if (strlen(dirbuf->d_name) > baselen &&		/* Match? */
+		    !StrNCmp(dirbuf->d_name, base, baselen)) {
+		    HTAtom *enc;
+		    HTFormat rep = HTFileFormat(dirbuf->d_name, &enc, NULL);
+		    float filevalue = HTFileValue(dirbuf->d_name);
+		    float value = HTStackValue(rep, format_out,
+					       filevalue,
+					       0L /* @@@@@@ */ );
+
+		    if (value <= 0.0) {
+			int rootlen = 0;
+			const char *atomname = NULL;
+			CompressFileType cft =
+			HTCompressFileType(dirbuf->d_name, ".", &rootlen);
+			char *cp = NULL;
+
+			enc = NULL;
+			if (cft != cftNone) {
+			    StrAllocCopy(cp, dirbuf->d_name);
+			    cp[rootlen] = '\0';
+			    format = HTFileFormat(cp, NULL, NULL);
+			    FREE(cp);
+			    value = HTStackValue(format, format_out,
+						 filevalue, 0L);
+			    switch (cft) {
+			    case cftCompress:
+				atomname = "application/x-compressed";
+				break;
+			    case cftGzip:
+				atomname = "application/x-gzip";
+				break;
+			    case cftDeflate:
+				atomname = "application/x-deflate";
+				break;
+			    case cftBzip2:
+				atomname = "application/x-bzip2";
+				break;
+			    case cftNone:
+				break;
+			    }
+			}
+
+			if (atomname != NULL) {
+			    value = HTStackValue(format, format_out,
+						 filevalue, 0L);
+			    if (value <= 0.0) {
+				format = HTAtom_for(atomname);
+				value = HTStackValue(format, format_out,
+						     filevalue, 0L);
+			    }
+			    if (value <= 0.0) {
+				format = HTAtom_for("www/compressed");
+				value = HTStackValue(format, format_out,
+						     filevalue, 0L);
+			    }
+			}
+		    }
+		    if (value < NO_VALUE_FOUND) {
+			CTRACE((tfp,
+				"HTLoadFile: value of presenting %s is %f\n",
+				HTAtom_name(rep), value));
+			if (value > best) {
+			    best_rep = rep;
+			    best_enc = enc;
+			    best = value;
+			    StrAllocCopy(best_name, dirbuf->d_name);
+			}
+		    }		/* if best so far */
+		}
+		/* if match */
+	    }			/* end while directory entries left to read */
+	    closedir(dp);
+
+	    if (best_rep) {
+		format = best_rep;
+		myEncoding = best_enc;
+		base[-1] = '/';	/* Restore directory name */
+		base[0] = '\0';
+		StrAllocCat(localname, best_name);
+		FREE(best_name);
+	    } else {		/* If not found suitable file */
+		FREE(localname);
+		FREE(nodename);
+		return HTLoadError(sink, 403, FAILED_NO_REPRESENTATION);
+	    }
+	    /*NOTREACHED */
+	}
+	/* if multi suffix */
+	/*
+	 * Check to see if the 'localname' is in fact a directory.  If it is
+	 * create a new hypertext object containing a list of files and
+	 * subdirectories contained in the directory.  All of these are links
+	 * to the directories or files listed.  NB This assumes the existence
+	 * of a type 'STRUCT_DIRENT', which will hold the directory entry, and
+	 * a type 'DIR' which is used to point to the current directory being
+	 * read.
+	 */
+#if defined(USE_DOS_DRIVES)
+	if (strlen(localname) == 2 && LYIsDosDrive(localname))
+	    LYAddPathSep(&localname);
+#endif
+	if (HTStat(localname, &dir_info) == -1)		/* get file information */
+	{
+	    /* if can't read file information */
+	    CTRACE((tfp, "HTLoadFile: can't stat %s\n", localname));
+
+	} else {		/* Stat was OK */
+
+	    if (S_ISDIR(dir_info.st_mode)) {
+		/*
+		 * If localname is a directory.
+		 */
+		DIR *dp;
+		struct stat file_info;
+
+		CTRACE((tfp, "%s is a directory\n", localname));
+
+		/*
+		 * Check directory access.  Selective access means only those
+		 * directories containing a marker file can be browsed.
+		 */
+		if (HTDirAccess == HT_DIR_FORBID) {
+		    FREE(localname);
+		    FREE(nodename);
+		    return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN);
+		}
+
+		if (HTDirAccess == HT_DIR_SELECTIVE) {
+		    char *enable_file_name = NULL;
+
+		    HTSprintf0(&enable_file_name, "%s/%s", localname, HT_DIR_ENABLE_FILE);
+		    if (stat(enable_file_name, &file_info) != 0) {
+			FREE(localname);
+			FREE(nodename);
+			FREE(enable_file_name);
+			return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS);
+		    }
+		}
+
+		CTRACE((tfp, "Opening directory %s\n", localname));
+		dp = opendir(localname);
+		if (!dp) {
+		    FREE(localname);
+		    FREE(nodename);
+		    return HTLoadError(sink, 403, FAILED_DIR_UNREADABLE);
+		}
+
+		/*
+		 * Directory access is allowed and possible.
+		 */
+
+		status = print_local_dir(dp, localname,
+					 anchor, format_out, sink);
+		closedir(dp);
+		FREE(localname);
+		FREE(nodename);
+		return status;	/* document loaded, maybe partial */
+
+	    }
+	    /* end if localname is a directory */
+	    if (S_ISREG(dir_info.st_mode)) {
+#ifdef LONG_MAX
+		if (dir_info.st_size <= LONG_MAX)
+#endif
+		    anchor->content_length = (long) dir_info.st_size;
+	    }
+
+	}			/* end if file stat worked */
+
+/* End of directory reading section
+*/
+#endif /* HAVE_READDIR */
+	if (decompressAndParse(anchor,
+			       format_out,
+			       sink,
+			       nodename,
+			       localname,
+			       myEncoding,
+			       format,
+			       &status)) {
+	    FREE(nodename);
+	    FREE(localname);
+	    return status;
+	}
+	FREE(localname);
+    }				/* local unix file system */
+#endif /* !NO_UNIX_IO */
+#endif /* VMS */
+
+#ifndef DECNET
+    /*
+     * Now, as transparently mounted access has failed, we try FTP.
+     */
+    {
+	/*
+	 * Deal with case-sensitivity differences on VMS versus Unix.
+	 */
+#ifdef VMS
+	if (strcasecomp(nodename, HTHostName()) != 0)
+#else
+	if (strcmp(nodename, HTHostName()) != 0)
+#endif /* VMS */
+	{
+	    status = -1;
+	    FREE(nodename);
+	    if (StrNCmp(addr, "file://localhost", 16)) {
+		/* never go to ftp site when URL
+		 * is file://localhost
+		 */
+#ifndef DISABLE_FTP
+		status = HTFTPLoad(addr, anchor, format_out, sink);
+#endif /* DISABLE_FTP */
+	    }
+	    return status;
+	}
+	FREE(nodename);
+    }
+#endif /* !DECNET */
+
+    /*
+     * All attempts have failed.
+     */
+    {
+	CTRACE((tfp, "Can't open `%s', errno=%d\n", addr, SOCKET_ERRNO));
+
+	return HTLoadError(sink, 403, FAILED_FILE_UNREADABLE);
+    }
+}
+
+static const char *program_paths[pp_Last];
+
+/*
+ * Given a program number, return its path
+ */
+const char *HTGetProgramPath(ProgramPaths code)
+{
+    const char *result = NULL;
+
+    if (code > ppUnknown && code < pp_Last)
+	result = program_paths[code];
+    return result;
+}
+
+/*
+ * Store a program's path.  The caller must allocate the string used for 'path',
+ * since HTInitProgramPaths() may free it.
+ */
+void HTSetProgramPath(ProgramPaths code, const char *path)
+{
+    if (code > ppUnknown && code < pp_Last) {
+	program_paths[code] = isEmpty(path) ? 0 : path;
+    }
+}
+
+/*
+ * Reset the list of known program paths to the ones that are compiled-in
+ */
+void HTInitProgramPaths(BOOL init)
+{
+    ProgramPaths code;
+    int n;
+    const char *path;
+    const char *test;
+
+    for (n = (int) ppUnknown + 1; n < (int) pp_Last; ++n) {
+	switch (code = (ProgramPaths) n) {
+#ifdef BZIP2_PATH
+	case ppBZIP2:
+	    path = BZIP2_PATH;
+	    break;
+#endif
+#ifdef CHMOD_PATH
+	case ppCHMOD:
+	    path = CHMOD_PATH;
+	    break;
+#endif
+#ifdef COMPRESS_PATH
+	case ppCOMPRESS:
+	    path = COMPRESS_PATH;
+	    break;
+#endif
+#ifdef COPY_PATH
+	case ppCOPY:
+	    path = COPY_PATH;
+	    break;
+#endif
+#ifdef CSWING_PATH
+	case ppCSWING:
+	    path = CSWING_PATH;
+	    break;
+#endif
+#ifdef GZIP_PATH
+	case ppGZIP:
+	    path = GZIP_PATH;
+	    break;
+#endif
+#ifdef INFLATE_PATH
+	case ppINFLATE:
+	    path = INFLATE_PATH;
+	    break;
+#endif
+#ifdef INSTALL_PATH
+	case ppINSTALL:
+	    path = INSTALL_PATH;
+	    break;
+#endif
+#ifdef MKDIR_PATH
+	case ppMKDIR:
+	    path = MKDIR_PATH;
+	    break;
+#endif
+#ifdef MV_PATH
+	case ppMV:
+	    path = MV_PATH;
+	    break;
+#endif
+#ifdef RLOGIN_PATH
+	case ppRLOGIN:
+	    path = RLOGIN_PATH;
+	    break;
+#endif
+#ifdef RM_PATH
+	case ppRM:
+	    path = RM_PATH;
+	    break;
+#endif
+#ifdef RMDIR_PATH
+	case ppRMDIR:
+	    path = RMDIR_PATH;
+	    break;
+#endif
+#ifdef SETFONT_PATH
+	case ppSETFONT:
+	    path = SETFONT_PATH;
+	    break;
+#endif
+#ifdef TAR_PATH
+	case ppTAR:
+	    path = TAR_PATH;
+	    break;
+#endif
+#ifdef TELNET_PATH
+	case ppTELNET:
+	    path = TELNET_PATH;
+	    break;
+#endif
+#ifdef TN3270_PATH
+	case ppTN3270:
+	    path = TN3270_PATH;
+	    break;
+#endif
+#ifdef TOUCH_PATH
+	case ppTOUCH:
+	    path = TOUCH_PATH;
+	    break;
+#endif
+#ifdef UNCOMPRESS_PATH
+	case ppUNCOMPRESS:
+	    path = UNCOMPRESS_PATH;
+	    break;
+#endif
+#ifdef UNZIP_PATH
+	case ppUNZIP:
+	    path = UNZIP_PATH;
+	    break;
+#endif
+#ifdef UUDECODE_PATH
+	case ppUUDECODE:
+	    path = UUDECODE_PATH;
+	    break;
+#endif
+#ifdef ZCAT_PATH
+	case ppZCAT:
+	    path = ZCAT_PATH;
+	    break;
+#endif
+#ifdef ZIP_PATH
+	case ppZIP:
+	    path = ZIP_PATH;
+	    break;
+#endif
+	default:
+	    path = NULL;
+	    break;
+	}
+	test = HTGetProgramPath(code);
+	if (test != NULL && test != path) {
+	    free((char *) test);
+	}
+	if (init) {
+	    HTSetProgramPath(code, path);
+	}
+    }
+}
+
+/*
+ *	Protocol descriptors
+ */
+#ifdef GLOBALDEF_IS_MACRO
+#define _HTFILE_C_1_INIT { "ftp", HTLoadFile, 0 }
+GLOBALDEF(HTProtocol, HTFTP, _HTFILE_C_1_INIT);
+#define _HTFILE_C_2_INIT { "file", HTLoadFile, HTFileSaveStream }
+GLOBALDEF(HTProtocol, HTFile, _HTFILE_C_2_INIT);
+#else
+GLOBALDEF HTProtocol HTFTP =
+{"ftp", HTLoadFile, 0};
+GLOBALDEF HTProtocol HTFile =
+{"file", HTLoadFile, HTFileSaveStream};
+#endif /* GLOBALDEF_IS_MACRO */