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.c1871
1 files changed, 1871 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c
new file mode 100644
index 00000000..9148f5d1
--- /dev/null
+++ b/WWW/Library/Implementation/HTFile.c
@@ -0,0 +1,1871 @@
+/*			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.
+*/
+
+#ifndef VMS
+/* #define LONG_LIST */ /* Define this for long style unix listings (ls -l) */
+/* #define NO_PARENT_DIR_REFERENCE */ /* Define this for no parent links */
+#endif /* !VMS */
+
+#include "HTUtils.h"
+#include "tcp.h"
+#include "HTFile.h"		/* Implemented here */
+#ifdef VMS
+#include <stat.h>
+#endif /* VMS */
+
+#ifndef VMS
+#ifdef LONG_LIST
+#include <pwd.h>
+#include <grp.h>
+#endif /* LONG_LIST */
+#endif /* !VMS */
+  
+#define INFINITY 512		/* file name length @@ FIXME */
+#define MULTI_SUFFIX ".multi"   /* Extension for scanning formats */
+
+#define HT_EM_SPACE ((char)2)
+
+#define FREE(x) if (x) {free(x); x = NULL;}
+
+#ifdef VMS
+#include "HTVMSUtils.h"
+#endif /* VMS */
+
+#include "HTParse.h"
+#include "HTTCP.h"
+#ifndef DECNET
+#include "HTFTP.h"
+#endif /* !DECNET */
+#include "HTAnchor.h"
+#include "HTAtom.h"
+#include "HTWriter.h"
+#include "HTFWriter.h"
+#include "HTInit.h"
+#include "HTBTree.h"
+#include "HTAlert.h"
+#include "HTCJK.h"
+
+#include "LYexit.h"
+#include "LYLeaks.h"
+
+typedef struct _HTSuffix {
+	char *		suffix;
+	HTAtom *	rep;
+	HTAtom *	encoding;
+	float		quality;
+} HTSuffix;
+
+#ifndef NGROUPS
+#ifdef NGROUPS_MAX
+#define NGROUPS NGROUPS_MAX
+#else
+#define NGROUPS 32
+#endif /* NGROUPS_MAX */
+#endif /* NGROUPS */
+
+
+#ifdef USE_DIRENT		/* Set this for Sys V systems */
+#define STRUCT_DIRENT struct dirent
+#else
+#define STRUCT_DIRENT struct direct
+#endif /* USE_DIRENT */
+
+#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, 0)
+#define END(e) (*target->isa->end_element)(target, e, 0)
+#define FREE_TARGET (*target->isa->_free)(target)
+struct _HTStructured {
+	CONST HTStructuredClass *	isa;
+	/* ... */
+};
+
+
+/*                   Controlling globals
+**
+*/
+
+PUBLIC int HTDirAccess = HT_DIR_OK;
+
+#ifdef DIRED_SUPPORT
+PUBLIC int HTDirReadme = HT_DIR_README_NONE;
+#define FILES_FIRST 1
+#define MIXED_STYLE 2
+extern BOOLEAN lynx_edit_mode;
+extern BOOLEAN dir_list_style;
+#else
+PUBLIC int HTDirReadme = HT_DIR_README_TOP;
+#endif /* DIRED_SUPPORT */
+
+extern int current_char_set;
+extern char *LYchar_set_names[];
+extern BOOL HTPassEightBitRaw;
+extern HTCJKlang HTCJK;
+
+PRIVATE char *HTMountRoot = "/Net/";		/* Where to find mounts */
+#ifdef VMS
+PRIVATE char *HTCacheRoot = "/WWW$SCRATCH";   /* Where to cache things */
+#else
+PRIVATE char *HTCacheRoot = "/tmp/W3_Cache_";   /* Where to cache things */
+#endif /* VMS */
+
+/* PRIVATE char *HTSaveRoot  = "$(HOME)/WWW/";*/    /* Where to save things */
+
+
+/*	Suffix registration
+*/
+
+PRIVATE HTList * HTSuffixes = 0;
+PRIVATE HTSuffix no_suffix = { "*", NULL, NULL, 1.0 };
+PRIVATE HTSuffix unknown_suffix = { "*.*", NULL, NULL, 1.0};
+
+/*
+ *	To free up the suffixes at program exit.
+ */
+PRIVATE void free_suffixes NOPARAMS;
+
+#ifdef LONG_LIST
+PRIVATE void LYListFmtParse ARGS5(
+	char *,		fmtstr,
+	char *,		file,
+	HTStructured *,	target,
+	char *,		entry,
+	char *,		tail)
+{
+	char c;
+	char *s;
+	char *end;
+	char *start;
+	char *str = NULL;
+	struct stat st;
+	char buf[512];
+	char fmt[512];
+	char type;
+	struct passwd *p;
+	struct group *g;
+	time_t now;
+	char *datestr;
+	int len;
+#define SEC_PER_YEAR	(60 * 60 * 24 * 365)
+	static char *pbits[] = { "---", "--x", "-w-", "-wx",
+		"r--", "r-x", "rw-", "rwx", 0 };
+	static 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]
+
+	if (lstat(file, &st) < 0)
+		fmtstr = "%a";	/* can't stat so just do anchor */
+	
+	StrAllocCopy(str, fmtstr);
+	s = str;
+	end = str + strlen(str);
+	START(HTML_PRE);
+	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(*s) || *s == '.' || *s == '-')
+			s++;
+		c = *s;		/* the format char. or \0 */
+		*s = '\0';
+
+		switch (c) {
+		case '\0':
+			break;
+		
+		case 'A':
+		case 'a':	/* anchor */
+			HTDirEntry(target, tail, entry);
+			sprintf(fmt, "%%%ss", start);
+			sprintf(buf, fmt, entry);
+			PUTS(buf);
+			END(HTML_A);
+			if (c != 'A' && (st.st_mode & S_IFMT) == S_IFLNK &&
+			    (len = readlink(file, buf, sizeof(buf))) >= 0) {
+				PUTS(" -> ");
+				buf[len] = '\0';
+				PUTS(buf);
+			}
+			*buf = '\0';
+			break;
+
+		case 'd':	/* date */
+			now = time(0);
+			datestr = ctime(&st.st_mtime);
+			if ((now - st.st_mtime) < SEC_PER_YEAR/2)
+				/* MMM DD HH:MM */
+				sprintf(buf, "%.12s", datestr + 4);
+			else
+				/* MMM DD  YYYY */
+				sprintf(buf, "%.7s %.4s ", datestr + 4,
+				  datestr + 20);
+			sprintf(fmt, "%%%ss", start);
+			sprintf(buf, fmt, buf);
+			break;
+
+		case 's':	/* size in bytes */
+			sprintf(fmt, "%%%sd", start);
+			sprintf(buf, fmt, st.st_size);
+			break;
+
+		case 'K':	/* size in Kilobytes but not for directories */
+			if ((st.st_mode & S_IFMT) == S_IFDIR) {
+				sprintf(fmt, "%%%ss ", start);
+				sprintf(buf, fmt, "");
+				break;
+			}
+			/* FALL THROUGH */
+		case 'k':	/* size in Kilobytes */
+			sprintf(fmt, "%%%sdK", start);
+			sprintf(buf, fmt, (st.st_size+1023)/1024);
+			break;
+
+		case 'p':	/* unix-style permission bits */
+			switch(st.st_mode & S_IFMT) {
+			case S_IFIFO: type = 'p'; break;
+			case S_IFCHR: type = 'c'; break;
+			case S_IFDIR: type = 'd'; break;
+			case S_IFBLK: type = 'b'; break;
+			case S_IFREG: type = '-'; break;
+			case S_IFLNK: type = 'l'; break;
+#ifdef S_IFSOCK
+			case S_IFSOCK: type = 's'; break;
+#endif /* S_IFSOCK */
+			default: type = '?'; break;
+			}
+			sprintf(buf, "%c%s%s%s", type,
+			  PBIT(st.st_mode, 6, st.st_mode & S_ISUID),
+			  PBIT(st.st_mode, 3, st.st_mode & S_ISGID),
+			  PBIT(st.st_mode, 0, 0));
+			sprintf(fmt, "%%%ss", start);
+			sprintf(buf, fmt, buf);
+			break;
+
+		case 'o':	/* owner */
+			sprintf(fmt, "%%%ss", start);
+			p = getpwuid(st.st_uid);
+			if (p) {
+				sprintf(fmt, "%%%ss", start);
+				sprintf(buf, fmt, p->pw_name);
+			} else {
+				
+				sprintf(fmt, "%%%sd", start);
+				sprintf(buf, fmt, st.st_uid);
+			}
+			break;
+
+		case 'g':	/* group */
+			g = getgrgid(st.st_gid);
+			if (g) {
+				sprintf(fmt, "%%%ss", start);
+				sprintf(buf, fmt, g->gr_name);
+			} else {
+				sprintf(fmt, "%%%sd", start);
+				sprintf(buf, fmt, st.st_gid);
+			}
+			break;
+
+		case 'l':	/* link count */
+			sprintf(fmt, "%%%sd", start);
+			sprintf(buf, fmt, st.st_nlink);
+			break;
+
+		default:
+			fprintf(stderr,
+			"Unknown format character `%c' in list format\n", c);
+			break;
+		}
+		PUTS(buf);
+
+		s++;
+	}
+	END(HTML_PRE);
+	PUTS("\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 ".".
+**
+**	If filename suffix is already defined its previous
+**	definition is overridden.
+*/
+PUBLIC void HTSetSuffix ARGS4(
+	CONST char *,	suffix,
+	CONST char *,	representation,
+	CONST char *,	encoding,
+	float,		value)
+{
+    HTSuffix * suff;
+
+    if (strcmp(suffix, "*") == 0)
+        suff = &no_suffix;
+    else if (strcmp(suffix, "*.*") == 0)
+        suff = &unknown_suffix;
+    else {
+	HTList *cur = HTSuffixes;
+
+	while (NULL != (suff = (HTSuffix*)HTList_nextObject(cur))) {
+	    if (suff->suffix && 0 == strcmp(suff->suffix, suffix))
+		break;
+	}
+	if (!suff) { /* Not found -- create a new node */
+	    suff = (HTSuffix *) calloc(1, sizeof(HTSuffix));
+	    if (suff == NULL)
+	        outofmem(__FILE__, "HTSetSuffix");
+
+	    /*
+             *	Memory leak fixed.
+	     *	05-28-94 Lynx 2-3-1 Garrett Arch Blythe
+	     */	
+	    if (!HTSuffixes)	{
+		HTSuffixes = HTList_new();
+		atexit(free_suffixes);
+	    }
+
+	    HTList_addObject(HTSuffixes, suff);
+	
+	    StrAllocCopy(suff->suffix, suffix);
+	}
+    }
+
+    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);
+    
+    suff->quality = value;
+}
+
+/*
+**	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
+*/
+PRIVATE void free_suffixes NOARGS
+{
+    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);
+    }
+    /*
+     *  Free off the list itself.
+     */
+    HTList_delete(HTSuffixes);
+    HTSuffixes = NULL;
+}
+
+
+/*	Send README file
+**
+**  If a README file exists, then it is inserted into the document here.
+*/
+#ifdef GOT_READ_DIR
+PRIVATE void do_readme ARGS2(HTStructured *, target, CONST char *, localname)
+{ 
+    FILE * fp;
+    char * readme_file_name = 
+	malloc(strlen(localname)+ 1 + strlen(HT_DIR_README_FILE) + 1);
+    strcpy(readme_file_name, localname);
+    strcat(readme_file_name, "/");
+    strcat(readme_file_name, HT_DIR_README_FILE);
+    
+    fp = fopen(readme_file_name,  "r");
+    
+    if (fp) {
+	HTStructuredClass targetClass;
+	
+	targetClass =  *target->isa;	/* (Can't init agregate in K&R) */
+	START(HTML_PRE);
+	for (;;){
+	    char c = fgetc(fp);
+	    if (c == (char)EOF) break;
+#ifdef NOTDEFINED
+	    switch (c) {
+	    	case '&':
+		case '<':
+		case '>':
+			PUTC('&');
+			PUTC('#');
+			PUTC((char)(c / 10));
+			PUTC((char) (c % 10));
+			PUTC(';');
+			break;
+/*	    	case '\n':
+			PUTC('\r');    
+Bug removed thanks to joe@athena.mit.edu */			
+		default:
+			PUTC(c);
+	    }
+#else
+	    PUTC(c);
+#endif /* NOTDEFINED */
+	}
+	END(HTML_PRE);
+	fclose(fp);
+    } 
+}
+#endif /* GOT_READ_DIR */
+
+
+/*	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.
+*/
+PUBLIC char * HTCacheFileName ARGS1(
+	CONST char *,	name)
+{
+    char * access = HTParse(name, "", PARSE_ACCESS);
+    char * host = HTParse(name, "", PARSE_HOST);
+    char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
+    
+    char * result;
+    result = (char *)malloc(
+	    strlen(HTCacheRoot)+strlen(access)
+	    +strlen(host)+strlen(path)+6+1);
+    if (result == NULL)
+        outofmem(__FILE__, "HTCacheFileName");
+    sprintf(result, "%s/WWW/%s/%s%s", HTCacheRoot, access, host, path);
+    FREE(path);
+    FREE(access);
+    FREE(host);
+    return result;
+}
+
+
+/*	Open a file for write, creating the path
+**	----------------------------------------
+*/
+#ifdef NOT_IMPLEMENTED
+PRIVATE int HTCreatePath ARGS1(CONST char *,path)
+{
+    return -1;
+}
+#endif /* NOT_IMPLEMENTED */
+
+/*	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.
+*/
+PUBLIC char * HTLocalName ARGS1(
+	CONST char *,	name)
+{
+    char * access = HTParse(name, "", PARSE_ACCESS);
+    char * host = HTParse(name, "", PARSE_HOST);
+    char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
+    
+    HTUnEscape(path);	/* Interpret % signs */
+    
+    if (0 == strcmp(access, "file")) { /* local file */
+        FREE(access);	
+	if ((0 == strcasecomp(host, HTHostName())) ||
+	    (0 == strcasecomp(host, "localhost")) || !*host) {
+	    FREE(host);
+	    if (TRACE)
+	        fprintf(stderr, "Node `%s' means path `%s'\n", name, path);
+	    return(path);
+	} else {
+	    char * result = (char *)malloc(
+	    			strlen("/Net/")+strlen(host)+strlen(path)+1);
+              if (result == NULL)
+	          outofmem(__FILE__, "HTLocalName");
+	    sprintf(result, "%s%s%s", "/Net/", host, path);
+	    FREE(host);
+	    FREE(path);
+	    if (TRACE)
+	        fprintf(stderr, "Node `%s' means file `%s'\n", name, result);
+	    return result;
+	}
+    } else {  /* other access */
+	char * result;
+#ifdef VMS
+        char * home =  getenv("HOME");
+	if (!home) 
+	    home = HTCacheRoot; 
+	else
+   	    home = HTVMS_wwwName(home);
+#else
+        CONST char * home =  (CONST char*)getenv("HOME");
+	if (!home)
+	    home = "/tmp"; 
+#endif /* VMS */
+	result = (char *)malloc(
+		strlen(home)+strlen(access)+strlen(host)+strlen(path)+6+1);
+        if (result == NULL)
+            outofmem(__FILE__, "HTLocalName");
+	sprintf(result, "%s/WWW/%s/%s%s", home, access, host, path);
+	FREE(path);
+	FREE(access);
+	FREE(host);
+	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.
+*/
+
+PUBLIC char * WWW_nameOfFile ARGS1(
+	CONST char *,	name)
+{
+    char * result;
+#ifdef NeXT
+    if (0 == strncmp("/private/Net/", name, 13)) {
+	result = (char *)malloc(7+strlen(name+13)+1);
+	if (result == NULL)
+	    outofmem(__FILE__, "WWW_nameOfFile");
+	sprintf(result, "file://%s", name+13);
+    } else
+#endif /* NeXT */
+    if (0 == strncmp(HTMountRoot, name, 5)) {
+	result = (char *)malloc(7+strlen(name+5)+1);
+	if (result == NULL)
+	    outofmem(__FILE__, "WWW_nameOfFile");
+	sprintf(result, "file://%s", name+5);
+    } else {
+        result = (char *)malloc(7+strlen(HTHostName())+strlen(name)+1);
+	if (result == NULL)
+	    outofmem(__FILE__, "WWW_nameOfFile");
+	sprintf(result, "file://%s%s", HTHostName(), name);
+    }
+    if (TRACE)
+        fprintf(stderr, "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
+**
+** On exit,
+**	returns	a pointer to a suitable suffix string if one has been
+**		found, else "".
+*/
+PUBLIC CONST char * HTFileSuffix ARGS1(
+	HTAtom*,	rep)
+{
+    HTSuffix * suff;
+    int n;
+    int i;
+
+#define NO_INIT  /* dont init anymore since I do it in Lynx at startup */
+#ifndef NO_INIT    
+    if (!HTSuffixes)
+        HTFileInit();
+#endif /* !NO_INIT */
+    n = HTList_count(HTSuffixes);
+    for (i = 0; i < n; i++) {
+	suff = (HTSuffix *)HTList_objectAt(HTSuffixes, i);
+	if (suff->rep == rep) {
+	    return suff->suffix;		/* OK -- found */
+	}
+    }
+    return "";		/* Dunno */
+}
+
+
+/*	Determine file format from file name
+**	------------------------------------
+**
+**	This version will return the representation and also set
+**	a variable for the encoding.
+**
+**	It will handle for example  x.txt, x.txt,Z, x.Z
+*/
+
+PUBLIC HTFormat HTFileFormat ARGS2(
+	CONST char *,	filename,
+	HTAtom **,	pencoding)
+{
+    HTSuffix * suff;
+    int n;
+    int i;
+    int lf;
+#ifdef VMS
+    char *semicolon = NULL;
+#endif /* VMS */
+    extern char LYforce_HTML_mode;
+
+    if (LYforce_HTML_mode) {
+        LYforce_HTML_mode = FALSE;
+        return WWW_HTML;
+    }
+
+#ifdef VMS
+    /*
+     * Trim at semicolon if a version number was
+     * included, so it doesn't interfere with the
+     * code for getting the MIME type. - FM
+     */
+    if ((semicolon = strchr(filename, ';')) != NULL)
+        *semicolon = '\0';
+#endif /* VMS */
+
+#ifndef NO_INIT    
+    if (!HTSuffixes)
+        HTFileInit();
+#endif /* !NO_INIT */
+    *pencoding = NULL;
+    lf  = strlen(filename);
+    n = HTList_count(HTSuffixes);
+    for (i = 0; i < n; i++) {
+        int ls;
+	suff = (HTSuffix *)HTList_objectAt(HTSuffixes, i);
+	ls = strlen(suff->suffix);
+	if ((ls <= lf) && 0 == strcasecomp(suff->suffix, filename + lf - ls)) {
+	    int j;
+	    *pencoding = suff->encoding;
+	    if (suff->rep) {
+#ifdef VMS
+		if (semicolon != NULL)
+		    *semicolon = ';';
+#endif /* VMS */
+	        return suff->rep;		/* OK -- found */
+	    }
+	    for (j = 0; j < n; j++) {  /* Got encoding, need representation */
+		int ls2;
+		suff = (HTSuffix *)HTList_objectAt(HTSuffixes, j);
+		ls2 = strlen(suff->suffix);
+		if ((ls <= lf) && 0 == strncasecomp(
+			suff->suffix, filename + lf - ls -ls2, ls2)) {
+		    if (suff->rep) {
+#ifdef VMS
+			if (semicolon != NULL)
+			    *semicolon = ';';
+#endif /* VMS */
+		        return suff->rep;
+		    }
+		}
+	    }
+	    
+	}
+    }
+    
+    /* defaults tree */
+    
+    suff = strchr(filename, '.') ? 	/* Unknown suffix */
+    	 ( unknown_suffix.rep ? &unknown_suffix : &no_suffix)
+	 : &no_suffix;
+	 
+    /* set default encoding unless found with suffix already */
+    if (!*pencoding)
+        *pencoding = suff->encoding ? suff->encoding
+				    : HTAtom_for("binary");
+#ifdef VMS
+    if (semicolon != NULL)
+        *semicolon = ';';
+#endif /* VMS */
+    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
+*/
+PUBLIC HTFormat HTCharsetFormat ARGS2(
+	HTFormat,		format,
+	HTParentAnchor *,	anchor)
+
+{
+    char *cp = NULL, *cp1, *cp2;
+    int i;
+
+    FREE(anchor->charset);
+    StrAllocCopy(cp, format->name);
+    for (i = 0; cp[i]; i++)
+	cp[i] = TOLOWER(cp[i]);
+    if (((cp1 = strchr(cp, ';')) != NULL) &&
+	(cp2 = strstr(cp1, "charset")) != NULL) {
+	if (TRACE)
+	    fprintf(stderr,
+	    	    "HTCharsetFormat: Extended MIME Content-Type is %s\n",
+		    format->name);
+	cp2 += 7;
+	while (*cp2 == ' ' || *cp2 == '=')
+	    cp2++;
+	if (!strncmp(cp2, "us-ascii", 8) ||
+	    !strncmp(cp2, "iso-8859-1", 10)) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-8859-1");
+	    HTCJK = NOCJK;
+	} else if (!strncmp(cp2, "iso-8859-2", 10) &&
+		   !strncmp(LYchar_set_names[current_char_set],
+			    "ISO Latin 2", 11)) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-8859-2");
+	    HTPassEightBitRaw = TRUE;
+	} else if (!strncmp(cp2, "iso-8859-", 9) &&
+		   !strncmp(LYchar_set_names[current_char_set],
+			    "Other ISO Latin", 15)) {
+	    /*
+	    **  Hope it's a match, for now. - FM
+	    */
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-8859- ");
+	    anchor->charset[9] = cp2[9];
+	    HTPassEightBitRaw = TRUE;
+	    HTAlert(anchor->charset);
+	} else if (!strncmp(cp2, "koi8-r", 6) &&
+		   !strncmp(LYchar_set_names[current_char_set],
+			    "KOI8-R character set", 20)) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "koi8-r");
+	    HTPassEightBitRaw = TRUE;
+	} else if (!strncmp(cp2, "euc-jp", 6) &&
+		   HTCJK == JAPANESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "euc-jp");
+	} else if (!strncmp(cp2, "shift_jis", 9) &&
+		   HTCJK == JAPANESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "shift_jis");
+	} else if (!strncmp(cp2, "iso-2022-jp", 11) &&
+		   HTCJK == JAPANESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-2022-jp");
+	} else if (!strncmp(cp2, "iso-2022-jp-2", 13) &&
+		   HTCJK == JAPANESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-2022-jp-2");
+	} else if (!strncmp(cp2, "euc-kr", 6) &&
+		   HTCJK == KOREAN) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "euc-kr");
+	} else if (!strncmp(cp2, "iso-2022-kr", 11) &&
+		   HTCJK == KOREAN) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-2022-kr");
+	} else if ((!strncmp(cp2, "big5", 4) ||
+		    !strncmp(cp2, "cn-big5", 7)) &&
+		   HTCJK == TAIPEI) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "big5");
+	} else if (!strncmp(cp2, "euc-cn", 6) &&
+		   HTCJK == CHINESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "euc-cn");
+	} else if ((!strncmp(cp2, "gb2312", 6) ||
+		    !strncmp(cp2, "cn-gb", 5)) &&
+		   HTCJK == CHINESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "gb2312");
+	} else if (!strncmp(cp2, "iso-2022-cn", 11) &&
+		   HTCJK == CHINESE) {
+	    *cp1 = '\0';
+	    format = HTAtom_for(cp);
+	    StrAllocCopy(anchor->charset, "iso-2022-cn");
+	}
+    }
+    FREE(cp);
+
+    return format;
+}
+
+
+/*	Determine value from file name
+**	------------------------------
+**
+*/
+
+PUBLIC float HTFileValue ARGS1(
+	CONST char *,	filename)
+{
+    HTSuffix * suff;
+    int n;
+    int i;
+    int lf = 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 = strlen(suff->suffix);
+	if ((ls <= lf) && 0==strcmp(suff->suffix, filename + lf - ls)) {
+	    if (TRACE)
+	        fprintf(stderr, "File: Value of %s is %.3f\n",
+			        filename, suff->quality);
+	    return suff->quality;		/* OK -- found */
+	}
+    }
+    return 0.3;		/* Dunno! */
+}
+
+
+/*	Determine write access to a file
+**	--------------------------------
+**
+** On exit,
+**	return value	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?
+*/
+
+#ifdef VMS
+#define NO_GROUPS
+#endif /* VMS */
+#ifdef NO_UNIX_IO
+#define NO_GROUPS
+#endif /* NO_UNIX_IO */
+#ifdef PCNFS
+#define NO_GROUPS
+#endif /* PCNFS */
+
+PUBLIC BOOL HTEditable ARGS1(
+	CONST char *,	filename)
+{
+#ifdef NO_GROUPS
+    return NO;		/* Safe answer till we find the correct algorithm */
+#else
+#ifdef NeXT
+    int		groups[NGROUPS];
+#else
+    gid_t	groups[NGROUPS];
+#endif /* NeXT */
+    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 i;
+	fprintf(stderr, 
+	    "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (",
+    	    (unsigned int) fileStatus.st_mode, fileStatus.st_uid,
+	    fileStatus.st_gid,
+	    myUid, ngroups);
+	for (i=0; i<ngroups; i++) fprintf(stderr, " %d", groups[i]);
+	fprintf(stderr, ")\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;
+	}
+    }
+    if (TRACE)
+        fprintf(stderr, "\tFile is not editable.\n");
+    return NO;					/* If no excuse, can't do */
+#endif /* NO_GROUPS */
+}
+
+
+/*	Make a save stream
+**	------------------
+**
+**	The stream must be used for writing back the file.
+**	@@@ no backup done
+*/
+PUBLIC HTStream * HTFileSaveStream ARGS1(
+	HTParentAnchor *,	anchor)
+{
+
+    CONST char * addr = HTAnchor_address((HTAnchor*)anchor);
+    char *  localname = HTLocalName(addr);
+    
+    FILE* fp = fopen(localname, "w");
+    if (!fp)
+        return NULL;
+    
+    return HTFWriter_new(fp);
+    
+}
+
+/*      Output one directory entry
+**
+*/
+PUBLIC void HTDirEntry ARGS3(
+	HTStructured *,	target,
+	CONST char *,	tail,
+	CONST char *,	entry)
+{
+    char * relative;
+    char * escaped = HTEscape(entry, URL_XPALPHAS);
+
+
+    if (tail == NULL || *tail == '\0') {
+        /* handle extra slash at end of path */
+        HTStartAnchor(target, NULL, escaped);
+    } else {
+        /* If empty tail, gives absolute ref below */
+        relative = (char*) malloc(strlen(tail) + strlen(escaped)+2);
+        if (relative == NULL)
+	    outofmem(__FILE__, "DirRead");
+        sprintf(relative, "%s/%s", tail, escaped);
+        HTStartAnchor(target, NULL, relative);
+        FREE(relative);
+    }
+    FREE(escaped);
+}
+ 
+/*      Output parent directory entry
+**
+**    This gives the TITLE and H1 header, and also a link
+**    to the parent directory if appropriate.
+*/
+PUBLIC void HTDirTitles ARGS2(
+	HTStructured *,	target,
+	HTAnchor * ,	anchor)
+{
+    char * logical = HTAnchor_address(anchor);
+    char * path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION);
+    char * current;
+    char * cp = NULL;
+
+    /* 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';
+	}
+    }
+    current = strrchr(path, '/');	/* last part or "" */
+
+    {
+      char * printable = NULL;
+
+#ifdef DIRED_SUPPORT
+      if (0 == strncasecomp(path, "/%2F", 4))
+          StrAllocCopy(printable, (path+1));
+      else
+          StrAllocCopy(printable, path);
+      if (0 == strncasecomp(printable, "/vmsysu%2b", 10) ||
+          0 == strncasecomp(printable, "/anonymou.", 10)) {
+          StrAllocCopy(cp, (printable+1));
+	  StrAllocCopy(printable, cp);
+	  FREE(cp);
+      }
+#else 
+      StrAllocCopy(printable, (current + 1));
+#endif /* DIRED_SUPPORT */
+
+      START(HTML_HEAD);
+      PUTS("\n");
+      HTUnEscape(printable);
+      START(HTML_TITLE);
+      PUTS(*printable ? printable : "Welcome");
+      PUTS(" directory");
+      END(HTML_TITLE);
+      PUTS("\n");
+      END(HTML_HEAD);
+      PUTS("\n");
+
+#ifdef DIRED_SUPPORT
+      START(HTML_H2);
+      PUTS(*printable ? "Current directory is " : "");
+      PUTS(*printable ? printable : "Welcome");
+      END(HTML_H2);
+      PUTS("\n");
+#else
+      START(HTML_H1);
+      PUTS(*printable ? printable : "Welcome");
+      END(HTML_H1);
+      PUTS("\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(logical);
+	  FREE(path);
+	  return;
+      }
+      FREE(printable);
+    }
+
+#ifndef NO_PARENT_DIR_REFERENCE
+    /*  Make link back to parent directory
+     */
+
+    if (current && current[1]) {   /* was a slash AND something else too */
+        char * parent;
+	char * relative;
+	*current++ = 0;
+        parent = strrchr(path, '/');  /* penultimate slash */
+
+	if ((parent && 0 == strncasecomp(parent, "/%2F", 4)) ||
+	    0 == strncasecomp(current, "%2F", 3)) {
+	    FREE(logical);
+	    FREE(path);
+	    return;
+	} 
+
+	relative = (char*) malloc(strlen(current) + 4);
+	if (relative == NULL)
+	    outofmem(__FILE__, "DirRead");
+	sprintf(relative, "%s/..", current);
+#ifndef 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().
+	     */
+	    extern BOOLEAN LYisLocalFile PARAMS((char *logical));
+	    DIR  * dp=NULL;
+
+	    if (LYisLocalFile(logical)) {
+	        if ((dp = opendir(relative)) == NULL) {
+	            FREE(logical);
+	            FREE(relative);
+	            FREE(path);
+	            return;
+		}
+		if (dp)
+	    	    closedir(dp);
+	    }
+	}
+#endif /* !VMS */
+	HTStartAnchor(target, "", relative);
+	FREE(relative);
+
+#ifdef DIRED_SUPPORT
+	if (dir_list_style != MIXED_STYLE)
+#endif /* DIRED_SUPPORT */
+	    PUTS("Up to ");
+	if (parent) {
+#ifdef DIRED_SUPPORT
+	   if (dir_list_style == MIXED_STYLE) {
+	      PUTS("../");
+	   } else {
+#else
+	   {
+#endif /* DIRED_SUPPORT */
+	      char * printable = NULL;
+	      StrAllocCopy(printable, parent + 1);
+	      HTUnEscape(printable);
+	      PUTS(printable);
+	      FREE(printable);
+	   }
+	} else {
+	  PUTS("/");
+	}
+
+	END(HTML_A);
+    }
+#endif /* NO_PARENT_DIR_REFERENCE */
+
+    FREE(logical);
+    FREE(path);
+    return;
+}
+		
+
+
+/*	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 occured.
+**			HTLOADED	OK 
+**
+*/
+PUBLIC int HTLoadFile ARGS4(
+	CONST char *,		addr,
+	HTParentAnchor *,	anchor,
+	HTFormat,		format_out,
+	HTStream *,		sink)
+{
+    char * filename = NULL;
+    char * access = NULL;
+    HTFormat format;
+    char * nodename = NULL;
+    char * newname = NULL;	/* Simplified name of file */
+    HTAtom * encoding;		/* @@ not used yet */
+#ifdef VMS
+    struct stat stat_info;
+#else
+    extern char *list_format;
+#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.
+    */
+    access = HTParse(newname, "", PARSE_ACCESS);
+    if (strcmp("ftp", access) == 0 ||
+       (strcmp("localhost", nodename) != 0 &&
+#ifdef VMS
+        strcasecomp(nodename, HTHostName()) != 0))
+#else
+        strcmp(nodename, HTHostName()) != 0))
+#endif /* VMS */
+    {
+        FREE(newname);
+	FREE(filename);
+	FREE(nodename);
+        FREE(access);
+        return HTFTPLoad(addr, anchor, format_out, sink);
+    } else {
+        FREE(newname);
+        FREE(access);
+    }
+#ifdef VMS
+    HTUnEscape(filename);
+#endif /* VMS */
+
+    /*
+    ** Determine the format and encoding mapped to any suffix.
+    */
+    format = HTFileFormat(filename, &encoding);
+
+    /*
+    **  Check the format for an extended MIME charset value, and
+    **  act on it if present.  Otherwise, assume the ISO-8859-1
+    **  character set for local files.  If it's actually another
+    **  charset (e.g., ISO-8859-2 or KOI8-R) and the terminal is
+    **  using that, Lynx users should make the current character
+    **  set "ISO Latin 1" so that 8-bit characters are passed raw.
+    */
+    format = HTCharsetFormat(format, anchor);
+
+#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) {
+	if (TRACE)
+	    fprintf(stderr, "HTLoadFile: Can't stat %s\n", filename);
+    } else {
+	if (((stat_info.st_mode) & S_IFMT) == S_IFDIR) {
+	    if (HTDirAccess == HT_DIR_FORBID) {
+		FREE(filename);
+		FREE(nodename);
+		return HTLoadError(sink, 403,
+		"Directory browsing is not allowed.");
+	    }
+
+	    if (HTDirAccess == HT_DIR_SELECTIVE) {
+		char * enable_file_name = 
+		    malloc(strlen(filename)+ 1 +
+		    strlen(HT_DIR_ENABLE_FILE) + 1);
+		strcpy(enable_file_name, filename);
+		strcat(enable_file_name, "/");
+		strcat(enable_file_name, HT_DIR_ENABLE_FILE);
+		if (HTStat(enable_file_name, &stat_info) == -1) {
+		    FREE(filename);
+		    FREE(nodename);
+		    return HTLoadError(sink, 403,
+		    "Selective access is not enabled for this directory");
+		}
+	    }
+
+	    FREE(filename);
+	    FREE(nodename);
+	    return HTVMSBrowseDir(addr, anchor, format_out, sink);
+	}
+    }
+
+    /*
+    **  Assume that the file is in Unix-style syntax if it contains a '/'
+    **  after the leading one @@
+    */
+    {
+        FILE * fp;
+	char * vmsname = strchr(filename + 1, '/') ?
+		    HTVMS_name(nodename, filename) : filename + 1;
+	fp = fopen(vmsname, "r", "shr=put", "shr=upd");
+	
+        /*
+	**  If the file wasn't VMS syntax, then perhaps it is ultrix
+	*/
+	if (!fp) {
+	    char ultrixname[INFINITY];
+	    if (TRACE)
+	        fprintf(stderr, "HTLoadFile: Can't open as %s\n", vmsname);
+	    sprintf(ultrixname, "%s::\"%s\"", nodename, filename);
+  	    fp = fopen(ultrixname, "r", "shr=put", "shr=upd");
+	    if (!fp) {
+		if (TRACE)
+		    fprintf(stderr, "HTLoadFile: Can't open as %s\n",
+		    		    ultrixname);
+	    }
+	}
+        if (fp) {
+	    int len;
+	    char *cp = NULL;
+	    char *semicolon = NULL;
+
+	    if (HTEditable(vmsname)) {
+		HTAtom * put = HTAtom_for("PUT");
+		HTList * methods = HTAnchor_methods(anchor);
+		if (HTList_indexOf(methods, put) == (-1)) {
+	   	    HTList_addObject(methods, put);
+	        }
+	    }
+	    /*
+	     * Trim vmsname at semicolon if a version number was
+	     * included, so it doesn't interfere with the check
+	     * for a compressed file. - FM
+	     */
+	    if ((semicolon = strchr(vmsname, ';')) != NULL)
+	        *semicolon = '\0';
+	    /*
+	    **  Fake a Content-Encoding for compressed files. - FM
+	    */
+	    if ((len = strlen(vmsname)) > 2) {
+	        if ((vmsname[len - 1] == 'Z') &&
+		    (vmsname[len - 2] == '.' ||
+		     vmsname[len - 2] == '-' ||
+		     vmsname[len - 2] == '_') &&
+		    vmsname[len - 3] != ']' &&
+		    vmsname[len - 3] != ':') {
+		    StrAllocCopy(cp, vmsname);
+		    cp[len - 2] = '\0';
+		    format = HTFileFormat(cp, &encoding);
+		    FREE(cp);
+		    format = HTCharsetFormat(format, anchor);
+		    StrAllocCopy(anchor->content_type, format->name);
+		    StrAllocCopy(anchor->content_encoding, "x-compress");
+		    format = HTAtom_for("www/compressed");
+		} else if ((len > 3) &&
+			   !strcasecomp((char *)&vmsname[len - 2], "gz")) {
+		    if (vmsname[len - 3] == '.' ||
+		        vmsname[len - 3] == '-' ||
+			vmsname[len - 3] == '_') {
+			StrAllocCopy(cp, vmsname);
+			cp[len - 3] = '\0';
+			format = HTFileFormat(cp, &encoding);
+			FREE(cp);
+			format = HTCharsetFormat(format, anchor);
+			StrAllocCopy(anchor->content_type, format->name);
+			StrAllocCopy(anchor->content_encoding, "x-gzip");
+			format = HTAtom_for("www/compressed");
+		    }
+		}
+	    }
+	    if (semicolon != NULL)
+	        *semicolon = ';';
+	    FREE(filename);
+	    FREE(nodename);
+	    HTParseFile(format, format_out, anchor, fp, sink);
+	    fclose(fp);
+            return HT_LOADED;
+        }  /* If successfull open */
+	FREE(filename);
+    }
+
+#else /* Unix: */
+
+    FREE(filename);
+    
+    /*
+    **  For unix, we try to translate the name into the name of a
+    **  transparently mounted file.
+    **
+    **  Not allowed in secure (HTClienntHost) 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 GOT_READ_DIR
+        /*		  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;
+
+	    STRUCT_DIRENT * dirbuf;
+	    float best = NO_VALUE_FOUND;	/* So far best is bad */
+	    HTFormat best_rep = NULL;	/* Set when rep found */
+	    STRUCT_DIRENT best_dirbuf;	/* Best dir entry so far */
+
+	    char * base = strrchr(localname, '/');
+	    int baselen;
+
+	    if (!base || base == localname)
+	        goto forget_multi;
+	    *base++ = 0;		/* Just got directory name */
+	    baselen = strlen(base)- strlen(MULTI_SUFFIX);
+	    base[baselen] = 0;	/* Chop off suffix */
+
+	    dp = opendir(localname);
+	    if (!dp) {
+forget_multi:
+		FREE(localname);
+		FREE(nodename);
+		return HTLoadError(sink, 500,
+			"Multiformat: directory scan failed.");
+	    }
+	    
+	    while ((dirbuf = readdir(dp))!=0) {
+		/* while there are directory entries to be read */
+		if (dirbuf->d_ino == 0)
+		    continue;	/* if the entry is not being used, skip it */
+
+		if ((int)strlen(dirbuf->d_name) > baselen &&     /* Match? */
+		    !strncmp(dirbuf->d_name, base, baselen)) {	
+		    HTFormat rep = HTFileFormat(dirbuf->d_name, &encoding);
+		    float value = HTStackValue(rep, format_out,
+		    				HTFileValue(dirbuf->d_name),
+						0.0  /* @@@@@@ */);
+		    if (value != NO_VALUE_FOUND) {
+		        if (TRACE)
+			    fprintf(stderr,
+				 "HTLoadFile: value of presenting %s is %f\n",
+				    HTAtom_name(rep), value);
+			if  (value > best) {
+			    best_rep = rep;
+			    best = value;
+			    best_dirbuf = *dirbuf;
+		       }
+		    }	/* if best so far */ 		    
+		 } /* if match */  
+		    
+	    } /* end while directory entries left to read */
+	    closedir(dp);
+	    
+	    if (best_rep) {
+		format = best_rep;
+		base[-1] = '/';		/* Restore directory name */
+		base[0] = 0;
+		StrAllocCat(localname, best_dirbuf.d_name);
+		goto open_file;
+		
+	    } else { 			/* If not found suitable file */
+		FREE(localname);
+		FREE(nodename);
+		return HTLoadError(sink, 403,	/* List formats? */
+		   "Could not find suitable representation for transmission.");
+	    }
+	    /*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 existance 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 (stat(localname,&dir_info) == -1) {     /* get file information */
+	                               /* if can't read file information */
+	    if (TRACE)
+	        fprintf(stderr, "HTLoadFile: can't stat %s\n", localname);
+
+	}  else {		/* Stat was OK */
+		
+	    if (((dir_info.st_mode) & S_IFMT) == S_IFDIR) {
+		/* if localname is a directory */	
+
+		HTStructured* target;		/* HTML object */
+		HTStructuredClass targetClass;
+
+		DIR *dp;
+		STRUCT_DIRENT * dirbuf;
+		
+		char * logical=0;
+		char * pathname=0;
+		char * tail=0;
+		
+		BOOL present[HTML_A_ATTRIBUTES];
+		
+		char * tmpfilename = NULL;
+		struct stat file_info;
+		
+		if (TRACE)
+		    fprintf(stderr,"%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,
+		    "Directory browsing is not allowed.");
+		}
+
+
+		if (HTDirAccess == HT_DIR_SELECTIVE) {
+		    char * enable_file_name = 
+			malloc(strlen(localname)+ 1 +
+			 strlen(HT_DIR_ENABLE_FILE) + 1);
+		    strcpy(enable_file_name, localname);
+		    strcat(enable_file_name, "/");
+		    strcat(enable_file_name, HT_DIR_ENABLE_FILE);
+		    if (stat(enable_file_name, &file_info) != 0) {
+			FREE(localname);
+			FREE(nodename);
+			return HTLoadError(sink, 403,
+			"Selective access is not enabled for this directory");
+		    }
+		}
+
+ 
+		dp = opendir(localname);
+		if (!dp) {
+		    FREE(localname);
+		    FREE(nodename);
+		    return HTLoadError(sink, 403, "This directory is not readable.");
+		}
+
+
+		/*
+		**  Directory access is allowed and possible.
+		*/
+		logical = HTAnchor_address((HTAnchor*)anchor);
+        	pathname = HTParse(logical, "", 
+					PARSE_PATH + PARSE_PUNCTUATION);
+
+    		if (!strcmp(pathname,"/"))  /* root path */
+        	    StrAllocCopy (tail, "/foo/..");
+    	    	else
+    		  {
+        	    char * p = strrchr(pathname, '/');  /* find lastslash */
+        	    StrAllocCopy(tail, p+1); /* take slash off the beginning */
+    		  }
+    		FREE(pathname);
+		
+		target = HTML_new(anchor, format_out, sink);
+		targetClass = *target->isa;	/* Copy routine entry points */
+		    
+  		{ int i;
+			for (i = 0; i < HTML_A_ATTRIBUTES; i++)
+				present[i] = (i==HTML_A_HREF);
+		}
+		
+                HTDirTitles(target, (HTAnchor *)anchor);
+
+#ifdef DIRED_SUPPORT
+		HTAnchor_setFormat((HTParentAnchor *) anchor, WWW_DIRED);
+		lynx_edit_mode = TRUE;
+#endif /* DIRED_SUPPORT */
+                if (HTDirReadme == HT_DIR_README_TOP)
+		    do_readme(target, localname);
+		{
+		    HTBTree * bt = HTBTree_new((HTComparer)strcmp);
+
+		    while ((dirbuf = readdir(dp))!=0)
+		    {
+			/* while there are directory entries to be read */
+		        HTBTElement * dirname = NULL;
+			extern BOOLEAN no_dotfiles, show_dotfiles;
+
+		        if (dirbuf->d_ino == 0)
+			    /* if the entry is not being used, skip it */
+			    continue;
+
+			if (strcmp(dirbuf->d_name, ".") == 0  /* skip self */
+#ifdef NO_PARENT_DIR_REFERENCE
+			    || strcmp(dirbuf->d_name, "..") == 0 ||
+#else
+			    || strcmp(dirbuf->d_name, "..") != 0 &&
+#endif
+			    ((no_dotfiles || !show_dotfiles) &&
+			     dirbuf->d_name[0] == '.'))
+			    /* skip those files whose name
+			     * begins with '.' */
+			    continue;
+
+			dirname = (HTBTElement *)malloc(
+					strlen(dirbuf->d_name) + 4);
+			if (dirname == NULL)
+			    outofmem(__FILE__,"DirRead");
+			StrAllocCopy(tmpfilename,localname);
+			if (strcmp(localname,"/")) 
+			    /* if filename is not root directory */
+			    StrAllocCat(tmpfilename,"/"); 
+
+			StrAllocCat(tmpfilename,dirbuf->d_name);
+			stat(tmpfilename, &file_info);
+			if (((file_info.st_mode) & S_IFMT) == S_IFDIR)
+#ifndef DIRED_SUPPORT
+		            sprintf((char *)dirname,"D%s",dirbuf->d_name);
+			else
+			    sprintf((char *)dirname,"F%s",dirbuf->d_name);
+			    /* D & F to have first directories, then files */
+#else
+			    if (dir_list_style == MIXED_STYLE)
+			        sprintf((char *)dirname," %s/",dirbuf->d_name);
+			    else
+			        sprintf((char *)dirname,"D%s",dirbuf->d_name);
+			else if (dir_list_style == MIXED_STYLE)
+			    sprintf((char *)dirname," %s",dirbuf->d_name);
+			else if (dir_list_style == FILES_FIRST)
+			    sprintf((char *)dirname,"C%s",dirbuf->d_name);
+			    /* C & D to have first files, then directories */
+			else
+			    sprintf((char *)dirname,"F%s",dirbuf->d_name);
+#endif /* !DIRED_SUPPORT */
+			HTBTree_add(bt,dirname); /* Sort dirname in the tree bt */
+		    }
+
+		    /*
+		    **  Run through tree printing out in order
+		    */
+		    {
+		        HTBTElement * next_element = HTBTree_next(bt,NULL);
+			    /* pick up the first element of the list */
+			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)
+		        {
+			    char *entry, *file_extra;
+
+			    StrAllocCopy(tmpfilename,localname);
+			    if (strcmp(localname,"/")) 
+
+					/* if filename is not root directory */
+			        StrAllocCat(tmpfilename,"/"); 
+
+			    StrAllocCat(tmpfilename,
+					(char *)HTBTree_object(next_element)+1);
+			    /* append the current entry's filename to the path */
+			    HTSimplify(tmpfilename);
+			    /* Output the directory entry */
+			    if (strcmp((char *)
+				       (HTBTree_object(next_element)),"D.."))
+			    {			    
+#ifdef DIRED_SUPPORT
+			        test = *(char *)(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); 
+				    } else if (dir_list_style != MIXED_STYLE)
+				       if (state == 'D')
+  					  END(HTML_DIR); 
+#endif /* !LONG_LIST */
+				    state = *(char *)
+				        (HTBTree_object(next_element))=='D'?'D':'F';
+				    START(HTML_H2);
+				    if (dir_list_style != MIXED_STYLE)
+				       PUTS(state == 'D'?"Directories:":"Files");
+#else
+				if (state != *(char *)(HTBTree_object(next_element))) 
+				{
+#ifndef LONG_LIST
+				    if (state == 'D')
+				        END(HTML_DIR);
+#endif /* !LONG_LIST */
+				    state = *(char *)
+				        (HTBTree_object(next_element))=='D'?'D':'F';
+				    START(HTML_H2);
+				    PUTS(state == 'D'?"Subdirectories:":"Files");
+#endif /* DIRED_SUPPORT */
+				    END(HTML_H2);
+#ifndef LONG_LIST
+				    START(HTML_DIR);
+#endif /* !LONG_LIST */
+				}
+#ifndef LONG_LIST
+			        START(HTML_LI);
+#endif /* !LONG_LIST */
+			    }
+			    entry = (char*)HTBTree_object(next_element)+1;
+			    file_extra = NULL;
+
+#ifdef LONG_LIST
+			    LYListFmtParse(list_format, tmpfilename, target,
+				entry, tail);
+#else
+			    HTDirEntry(target, tail, entry);
+			    PUTS(entry);
+			    END(HTML_A);
+			    if (file_extra) {
+				PUTS(file_extra);
+				FREE(file_extra);
+			    }
+#endif /* LONG_LIST */
+
+			    next_element = HTBTree_next(bt,next_element);
+			        /* pick up the next element of the list; 
+				 if none, return NULL*/
+			}
+			if (state == 'I')
+			{
+			    START(HTML_P);
+			    PUTS("Empty Directory");
+			}
+#ifndef LONG_LIST
+			else
+			    END(HTML_DIR);
+#endif /* !LONG_LIST */
+		    }
+
+		        /* end while directory entries left to read */
+		    closedir(dp);
+		    FREE(logical);
+		    FREE(tmpfilename);
+		    FREE(tail);
+		    HTBTreeAndObject_free(bt);
+
+		    if (HTDirReadme == HT_DIR_README_BOTTOM)
+			  do_readme(target, localname);
+		    FREE_TARGET;
+		    FREE(localname);
+		    FREE(nodename);
+		    return HT_LOADED;	/* document loaded */
+		}
+
+	    } /* end if localname is directory */
+	
+	} /* end if file stat worked */
+	
+/* End of directory reading section
+*/
+#endif /* GOT_READ_DIR */
+open_file:
+	{
+	    FILE * fp = fopen(localname,"r");
+
+	    if (TRACE)
+	        fprintf (stderr, "HTLoadFile: Opening `%s' gives %p\n",
+				 localname, (void*)fp);
+	    if (fp) {		/* Good! */
+	        int len;
+		char *cp = NULL;
+
+		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 ((len = strlen(localname)) > 2) {
+		    if (localname[len - 1] == 'Z' &&
+		        localname[len - 2] == '.') {
+			StrAllocCopy(cp, localname);
+			cp[len - 2] = '\0';
+			format = HTFileFormat(cp, &encoding);
+			FREE(cp);
+			format = HTCharsetFormat(format, anchor);
+			StrAllocCopy(anchor->content_type, format->name);
+			StrAllocCopy(anchor->content_encoding, "x-compress");
+			format = HTAtom_for("www/compressed");
+		    } else if ((len > 3) &&
+			       !strcasecomp((char *)&localname[len - 2],
+			       		    "gz") &&
+			       localname[len - 3] == '.') {
+			StrAllocCopy(cp, localname);
+			cp[len - 3] = '\0';
+			format = HTFileFormat(cp, &encoding);
+			FREE(cp);
+			format = HTCharsetFormat(format, anchor);
+			StrAllocCopy(anchor->content_type, format->name);
+			StrAllocCopy(anchor->content_encoding, "x-gzip");
+			format = HTAtom_for("www/compressed");
+		    }
+		}
+		FREE(localname);
+		FREE(nodename);
+		HTParseFile(format, format_out, anchor, fp, sink);
+		fclose(fp);
+		return HT_LOADED;
+	    }  /* If succesfull open */
+	}    /* scope of fp */
+    }  /* 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 verus Unix **/
+#ifdef VMS
+        if (strcasecomp(nodename, HTHostName()) != 0)
+#else
+	if (strcmp(nodename, HTHostName()) != 0)
+#endif /* VMS */
+	{
+	    FREE(nodename);
+	    if (!strncmp(addr, "file://localhost", 16)) {
+	        return -1;  /* never go to ftp site when URL
+			     * is file://localhost
+			     */
+	    } else {
+	        return HTFTPLoad(addr, anchor, format_out, sink);
+	    }
+	}
+	FREE(nodename);
+    }
+#endif /* !DECNET */
+
+/*	All attempts have failed.
+*/
+    {
+    	if (TRACE)
+	    fprintf(stderr, "Can't open `%s', errno=%d\n", addr, SOCKET_ERRNO);
+
+	return HTLoadError(sink, 403, "Can't access requested file.");
+    }
+    
+ 
+}
+
+/*		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 PUBLIC HTProtocol HTFTP  = { "ftp", HTLoadFile, 0 };
+GLOBALDEF PUBLIC HTProtocol HTFile = { "file", HTLoadFile, HTFileSaveStream };
+#endif /* GLOBALDEF_IS_MACRO */