/*
 * $LynxId: LYUtils.c,v 1.309 2024/01/15 17:10:52 tom Exp $
 */
#include <HTUtils.h>
#include <HTTCP.h>
#include <HTParse.h>
#include <HTAccess.h>
#include <HTCJK.h>
#include <HTAlert.h>

#if defined(__MINGW32__)

extern int kbhit(void);		/* FIXME: use conio.h */

#undef UNIX

#elif defined(_WINDOWS)

#ifdef DONT_USE_GETTEXT
#undef gettext
#elif defined(HAVE_GETTEXT)
#undef gettext
#define gettext conio_gettext
#else
#undef gettext
#endif

#include <conio.h>

#ifdef DONT_USE_GETTEXT
#define gettext(s) s
#elif defined(HAVE_GETTEXT)
#undef gettext
#ifdef _INTL_REDIRECT_MACROS
#define gettext libintl_gettext	/* restore definition from libintl.h */
#endif
#else
#undef gettext
#define gettext(s) s
#endif

#if !defined(kbhit) && defined(_WCONIO_DEFINED)
#define kbhit() _kbhit()	/* reasonably recent conio.h */
#endif
#elif defined(__minix)
#include <termios.h>		/* for struct winsize */

#endif /* __MINGW32__ */

#include <LYCurses.h>
#include <LYHistory.h>
#include <LYStrings.h>
#include <LYGlobalDefs.h>
#include <LYUtils.h>
#include <LYSignal.h>
#include <GridText.h>
#include <LYClean.h>
#include <LYCharSets.h>
#include <LYCharUtils.h>

#include <LYMainLoop.h>
#include <LYKeymap.h>

#ifdef __DJGPP__
#include <go32.h>
#include <sys/exceptn.h>
#endif /* __DJGPP__ */

#ifndef NO_GROUPS
#include <HTFile.h>
#endif

#ifdef _WINDOWS			/* 1998/04/30 (Thu) 19:04:25 */
#define GETPID()	(unsigned) (getpid() & 0xffff)
#else
#define GETPID()	(unsigned) getpid()
#endif /* _WINDOWS */

#ifdef FNAMES_8_3
#define PID_FMT "%04x"
#else
#define PID_FMT "%u"
#endif

#ifdef DJGPP_KEYHANDLER
#include <bios.h>
#endif /* DJGPP_KEYHANDLER */

#ifdef __EMX__
#  define BOOLEAN OS2_BOOLEAN	/* Conflicts, but is used */
#  undef HT_ERROR		/* Conflicts too */
#  define INCL_PM		/* I want some PM functions.. */
#  define INCL_DOSPROCESS	/* TIB PIB. */
#  include <os2.h>
#  undef BOOLEAN
#endif

#ifdef VMS
#include <descrip.h>
#include <libclidef.h>
#include <lib$routines.h>
#endif /* VMS */

#ifdef HAVE_UTMP
#include <pwd.h>
#ifdef UTMPX_FOR_UTMP
#include <utmpx.h>
#define utmp utmpx
#ifdef UTMPX_FILE
#ifdef UTMP_FILE
#undef UTMP_FILE
#endif /* UTMP_FILE */
#define UTMP_FILE UTMPX_FILE
#else
#ifdef __UTMPX_FILE
#define UTMP_FILE __UTMPX_FILE	/* at least in OS/390  S/390 -- gil -- 2100 */
#else
#ifndef UTMP_FILE
#define UTMP_FILE "/var/adm/utmpx"	/* Digital Unix 4.0 */
#endif
#endif
#endif /* UTMPX_FILE */
#else
#include <utmp.h>
#endif /* UTMPX_FOR_UTMP */
#endif /* HAVE_UTMP */

#ifdef NEED_PTEM_H
/* they neglected to define struct winsize in termios.h -- it's only in
 * termio.h and ptem.h (the former conflicts with other definitions).
 */
#include	<sys/stream.h>
#include	<sys/ptem.h>
#endif

#include <LYLeaks.h>

#ifdef USE_COLOR_STYLE
#include <AttrList.h>
#include <LYHash.h>
#include <LYStyle.h>
#endif

#ifdef SVR4_BSDSELECT
extern int BSDselect(int nfds, fd_set * readfds, fd_set * writefds,
		     fd_set * exceptfds, struct timeval *timeout);

#ifdef select
#undef select
#endif /* select */
#define select BSDselect
#ifdef SOCKS
#ifdef Rselect
#undef Rselect
#endif /* Rselect */
#define Rselect BSDselect
#endif /* SOCKS */
#endif /* SVR4_BSDSELECT */

#ifdef __DJGPP__
#undef select			/* defined to select_s in www_tcp.h */
#endif

#ifndef UTMP_FILE
#if defined(__FreeBSD__) || defined(__bsdi__)
#define UTMP_FILE _PATH_UTMP
#else
#define UTMP_FILE "/etc/utmp"
#endif /* __FreeBSD__ || __bsdi__ */
#endif /* !UTMP_FILE */

/*
 * experimental - make temporary filenames random to make the scheme less
 * obvious.  However, as noted by KW, there are instances (such as the
 * 'O'ption page, for which Lynx will store a temporary filename even when
 * it no longer applies, since it will reuse that filename at a later time.
 */
#ifdef USE_RAND_TEMPNAME
#if defined(LYNX_RAND_MAX)
#define HAVE_RAND_TEMPNAME 1
#define MAX_TEMPNAME 10000
#ifndef BITS_PER_CHAR
#define BITS_PER_CHAR 8
#endif
#endif
#endif

#define COPY_COMMAND "%s %s %s"

static HTList *localhost_aliases = NULL;	/* Hosts to treat as local */
static char *HomeDir = NULL;	/* HOME directory */

HTList *sug_filenames = NULL;	/* Suggested filenames   */

/*
 * Maintain a list of all of the temp-files we create so that we can remove
 * them during the cleanup.
 */
typedef struct _LYTemp {
    struct _LYTemp *next;
    char *name;
    BOOLEAN outs;
    FILE *file;
} LY_TEMP;

static LY_TEMP *ly_temp;

static LY_TEMP *FindTempfileByName(const char *name)
{
    LY_TEMP *p;

    for (p = ly_temp; p != 0; p = p->next) {
	if (!strcmp(p->name, name)) {
	    break;
	}
    }
    return p;
}

static LY_TEMP *FindTempfileByFP(FILE *fp)
{
    LY_TEMP *p;

    for (p = ly_temp; p != 0; p = p->next) {
	if (p->file == fp) {
	    break;
	}
    }
    return p;
}

#if defined(_WIN32)
/*
 * Use RegQueryValueExA() rather than RegQueryValueEx() for compatibility
 * with non-Unicode winvile
 */
static int w32_get_reg_sz(HKEY hkey, const char *name, char *value, unsigned length)
{
    int result;
    DWORD dwSzBuffer = length;

    CTRACE((tfp, "w32_get_reg_sz(%s)\n", name));
    result = RegQueryValueExA(hkey,
			      name,
			      NULL,
			      NULL,
			      (LPBYTE) value,
			      &dwSzBuffer);
    if (result == ERROR_SUCCESS) {
	value[dwSzBuffer] = 0;
	CTRACE((tfp, "->%s\n", value));
    }
    return result;
}

static char *w32_get_shell_folder(const char *name)
{
    static HKEY rootkey = HKEY_CURRENT_USER;

    char *result = 0;
    HKEY hkey;
    char buffer[LY_MAXPATH];

    if (RegOpenKeyEx(rootkey,
		     W32_STRING("Software"
				"\\Microsoft"
				"\\Windows"
				"\\CurrentVersion"
				"\\Explorer"
				"\\Shell Folders"),
		     0,
		     KEY_READ,
		     &hkey) == ERROR_SUCCESS) {
	if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) {

	    result = strdup(buffer);
	    (void) RegCloseKey(hkey);
	}

	(void) RegCloseKey(hkey);
    }
    return non_empty(result) ? result : 0;
}
#endif

/*
 * Get an environment variable, rejecting empty strings
 */
char *LYGetEnv(const char *name)
{
    char *result = getenv(name);

#if defined(_WIN32)
    if (result == 0) {
	static HKEY rootkeys[] =
	{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};

	int j;
	HKEY hkey;
	char buffer[256];

	for (j = 0; j < (int) TABLESIZE(rootkeys); ++j) {
	    if (RegOpenKeyEx(rootkeys[j],
			     LYNX_SUBKEY W32_STRING("\\Environment"),
			     0,
			     KEY_READ,
			     &hkey) == ERROR_SUCCESS) {
		if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) {

		    result = strdup(buffer);
		    (void) RegCloseKey(hkey);
		    break;
		}

		(void) RegCloseKey(hkey);
	    }
	}
    }
#endif
    return non_empty(result) ? result : 0;
}

/*
 * ascii versions of locale sensitive functions needed because in
 * Turkish locales tolower("I") is not "i". That's fatal for case
 * sensitive operations with charset names, HTML tags etc.
 */
#ifdef USE_ASCII_CTYPES
int ascii_tolower(int i)
{
    if (91 > i && i > 64)
	return (i + 32);
    else
	return i;
}

int ascii_toupper(int i)
{
    if (123 > i && i > 96)
	return (i - 32);
    else
	return i;
}

int ascii_isupper(int i)
{
    if (91 > i && i > 64)
	return 1;
    else
	return 0;
}
#endif /* USE_ASCII_CTYPES */

/*
 * Check for UTF-8 data, returning the length past the first character.
 * Return zero if we found an ordinary character rather than UTF-8.
 */
size_t utf8_length(int utf_flag,
		   const char *data)
{
    size_t utf_extra = 0;

    if (utf_flag && is8bits(*data)) {
	if ((*data & 0xe0) == 0xc0) {
	    utf_extra = 1;
	} else if ((*data & 0xf0) == 0xe0) {
	    utf_extra = 2;
	} else if ((*data & 0xf8) == 0xf0) {
	    utf_extra = 3;
	} else if ((*data & 0xfc) == 0xf8) {
	    utf_extra = 4;
	} else if ((*data & 0xfe) == 0xfc) {
	    utf_extra = 5;
	} else {
	    /*
	     * Garbage.
	     */
	    utf_extra = 0;
	}
	if (strlen(data + 1) < utf_extra) {
	    /*
	     * Shouldn't happen.
	     */
	    utf_extra = 0;
	}
    }
    return utf_extra;
}

/*
 * Free storage used for the link-highlighting.
 */
void LYFreeHilites(int first, int last)
{
    int i;

    for (i = first; i < last; i++) {
	LYSetHilite(i, NULL);
	FREE(links[i].lname);
    }
}

#define LXP (links[cur].lx)
#define LYP (links[cur].ly)

/*
 * Set the initial highlight information for a given link.
 */
void LYSetHilite(int cur,
		 const char *text)
{
    links[cur].list.hl_base.hl_text = DeConst(text);
    links[cur].list.hl_len = (short) ((text != NULL) ? 1 : 0);
    FREE(links[cur].list.hl_info);
}

/*
 * Add highlight information for the next line of a link.
 */
void LYAddHilite(int cur,
		 char *text,
		 int x)
{
    HiliteList *list = &(links[cur].list);
    HiliteInfo *have = list->hl_info;
    size_t need = (unsigned) (list->hl_len - 1);
    size_t want;

    list->hl_len = (short) (list->hl_len + 1);
    want = (size_t) list->hl_len;

    if (have != NULL) {
	have = typeRealloc(HiliteInfo, have, want);
    } else {
	have = typeMallocn(HiliteInfo, want);
    }
    list->hl_info = have;
    have[need].hl_text = text;
    have[need].hl_x = (short) x;
}

/*
 * Get the highlight text, counting from zero.
 */
const char *LYGetHiliteStr(int cur,
			   int count)
{
    const char *result;

    if (count >= links[cur].list.hl_len)
	result = NULL;
    else if (count > 0)
	result = links[cur].list.hl_info[count - 1].hl_text;
    else
	result = links[cur].list.hl_base.hl_text;
    return result;
}

/*
 * Get the X-ordinate at which to draw the corresponding highlight-text
 */
int LYGetHilitePos(int cur,
		   int count)
{
    int result;

    if (count >= links[cur].list.hl_len)
	result = -1;
    else if (count > 0)
	result = links[cur].list.hl_info[count - 1].hl_x;
    else
	result = LXP;
    return result;
}

#ifdef SHOW_WHEREIS_TARGETS

#define SKIP_GLYPHS(theFlag, theData, theOffset) \
	(theFlag \
	    ? LYmbcs_skip_glyphs(theData, (theOffset), theFlag) \
	    : (theData + (theOffset)))

/*
 * If we have an emphasized WHEREIS hit in the highlighted text, restore the
 * emphasis.  Note that we never emphasize the first and last characters of the
 * highlighted text when we are making the link current, so the link attributes
 * for the current link will persist at the beginning and end, providing an
 * indication to the user that it has been made current.  Also note that we use
 * HText_getFirstTargetInLine() to determine if there's a hit in the HText
 * structure line containing the link, and if so, get back a copy of the line
 * starting at that first hit (which might be before or after our link), and
 * with all IsSpecial characters stripped, so we don't need to deal with them
 * here.  -FM
 */
static BOOL show_whereis_targets(int flag,
				 int cur,
				 int count,
				 const char *target,
				 int TargetEmphasisON,
				 int utf_flag)
{
    const char *mydata = NULL;
    const char *cp;
    char *theData = NULL;
    char buffer[MAX_LINE];
    char tmp[7];
    int HitOffset;
    int LenNeeded;
    int Offset;
    int tLen;

    tmp[0] = tmp[1] = tmp[2] = '\0';

    if (non_empty(target)
	&& (links[cur].type & WWW_LINK_TYPE)
	&& non_empty(LYGetHiliteStr(cur, count))
	&& LYP + count < display_lines
	&& HText_getFirstTargetInLine(HTMainText,
				      links[cur].anchor_line_num + count,
				      utf_flag,
				      &Offset,
				      &tLen,
				      &theData,
				      target)) {
	int itmp, written, len, y, offset;
	const char *data;
	int tlen = (int) strlen(target);
	int hlen, hLen;
	int hLine = LYP + count;
	int hoffset = LYGetHilitePos(cur, count);
	size_t utf_extra = 0;

	/*
	 * Copy into the buffer only what will fit up to the right border of
	 * the screen.  -FM
	 */
	LYmbcsstrncpy(buffer,
		      NonNull(LYGetHiliteStr(cur, count)),
		      (int) (sizeof(buffer) - 1),
		      (LYcolLimit - LYGetHilitePos(cur, count)),
		      utf_flag);
	hlen = (int) strlen(buffer);
	hLen = ((IS_CJK_TTY || utf_flag) ?
		LYmbcsstrlen(buffer, utf_flag, YES) : hlen);

	/*
	 * Break out if the first hit in the line starts after this link.  -FM
	 */
	if (Offset < (hoffset + hLen)) {
	    /*
	     * Recursively skip hits that end before this link, and break out
	     * if there is no hit beyond those.  -FM
	     */
	    mydata = theData;
	    while ((Offset < hoffset) &&
		   ((Offset + tLen) <= hoffset)) {
		data = (mydata + tlen);
		offset = (Offset + tLen);
		if (((cp = LYno_attr_mb_strstr(data,
					       target,
					       utf_flag, YES,
					       &HitOffset,
					       &LenNeeded)) != NULL)
		    && (offset + LenNeeded) < LYcols) {
		    mydata = cp;
		    Offset = (offset + HitOffset);
		} else {
		    goto highlight_search_done;
		}
	    }
	    data = buffer;
	    offset = hoffset;

	    /*
	     * If the hit starts before the hightext, and ends in or beyond the
	     * hightext, restore the emphasis, skipping the first and last
	     * characters of the hightext if we're making the link current.
	     * -FM
	     */
	    if (offset >= 0 &&
		(Offset < offset) &&
		((Offset + tLen) > offset)) {
		itmp = 0;
		written = 0;
		len = (tlen - (offset - Offset));

		/*
		 * Go to the start of the hightext and handle its first
		 * character.  -FM
		 */
		LYmove(hLine, offset);
		tmp[0] = data[itmp];
		utf_extra = utf8_length(utf_flag, data + itmp);
		if (utf_extra) {
		    LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
		    itmp += (int) utf_extra;
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current.  -FM
		     */
		    if (flag != TRUE) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 1));
		    }
		    tmp[1] = '\0';
		    written += (int) (utf_extra + 1);
		} else if (IS_CJK_TTY && is8bits(tmp[0])) {
		    /*
		     * For CJK strings, by Masanobu Kimura.
		     */
		    tmp[1] = data[++itmp];
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current.  -FM
		     */
		    if (flag != TRUE) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 1));
		    }
		    tmp[1] = '\0';
		    written += 2;
		} else {
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current.  -FM
		     */
		    if (flag != TRUE) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 1));
		    }
		    written++;
		}
		itmp++;
		/*
		 * Start emphasis after the first character if we are making
		 * the link current and this is not the last character.  -FM
		 */
		if (!TargetEmphasisON &&
		    data[itmp] != '\0') {
		    LYstartTargetEmphasis();
		    TargetEmphasisON = TRUE;
		}

		/*
		 * Handle the remaining characters.  -FM
		 */
		for (;
		     written < len && (tmp[0] = data[itmp]) != '\0';
		     itmp++) {
		    /*
		     * Print all the other target chars, except the last
		     * character if it is also the last character of hightext
		     * and we are making the link current.  -FM
		     */
		    utf_extra = utf8_length(utf_flag, data + itmp);
		    if (utf_extra) {
			LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
			itmp += (int) utf_extra;
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    (void) y;
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			tmp[1] = '\0';
			written += (int) (utf_extra + 1);
		    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
			/*
			 * For CJK strings, by Masanobu Kimura.
			 */
			tmp[1] = data[++itmp];
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			tmp[1] = '\0';
			written += 2;
		    } else {
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			written++;
		    }
		}

		/*
		 * Stop the emphasis if we haven't already, then reset the
		 * offset to our current position in the line, and if that is
		 * beyond the link, or or we are making the link current and it
		 * is the last character of the hightext, we are done.  -FM
		 */
		if (TargetEmphasisON) {
		    LYstopTargetEmphasis();
		    TargetEmphasisON = FALSE;
		}
		LYGetYX(y, offset);
		if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
		/*
		 * See if we have another hit that starts within the
		 * hightext.  -FM
		 */
		    && ((cp =
			 LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
								mydata,
								offset - Offset),
					     target,
					     utf_flag, YES,
					     &HitOffset,
					     &LenNeeded)) != NULL)
		    && (offset + LenNeeded) < LYcols
		/*
		 * If the hit starts after the end of the hightext, or we
		 * are making the link current and the hit starts at its
		 * last character, we are done.  -FM
		 */
		    && (HitOffset + offset) <
		    (hoffset +
		     (flag == TRUE ? (hLen - 1) : hLen))) {
		    /*
		     * Set up the data and offset for the hit, and let the code
		     * for within hightext hits handle it.  -FM
		     */
		    mydata = cp;
		    Offset = (offset + HitOffset);
		    data = buffer;
		    offset = hoffset;
		    goto highlight_hit_within_hightext;
		}
		goto highlight_search_done;
	    }

	  highlight_hit_within_hightext:
	    /*
	     * If we get to here, the hit starts within the hightext.  If we
	     * are making the link current and it's the last character in the
	     * hightext, we are done.  Otherwise, move there and start
	     * restoring the emphasis.  -FM
	     */
	    if ((Offset - offset) <= (flag == TRUE ? (hLen - 1) : hLen)) {
		data = SKIP_GLYPHS(utf_flag, data, Offset - offset);
		if (utf_flag) {
		    LYrefresh();
		}
		offset = Offset;
		itmp = 0;
		written = 0;
		len = tlen;

		/*
		 * Go to the start of the hit and handle its first character.
		 * -FM
		 */
		LYmove(hLine, offset);
		tmp[0] = data[itmp];
		utf_extra = utf8_length(utf_flag, data + itmp);
		if (utf_extra) {
		    LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
		    itmp += (int) utf_extra;
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current, or we are making it current but this is not
		     * the first or last character of the hightext.  -FM
		     */
		    if (flag != TRUE ||
			(offset > hoffset && data[itmp + 1] != '\0')) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 1));
		    }
		    tmp[1] = '\0';
		    written += (int) (utf_extra + 1);
		} else if (IS_CJK_TTY && is8bits(tmp[0])) {
		    /*
		     * For CJK strings, by Masanobu Kimura.
		     */
		    tmp[1] = data[++itmp];
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current, or we are making it current but this is not
		     * the first or last character of the hightext.  -FM
		     */
		    if (flag != TRUE ||
			(offset > hoffset && data[itmp + 1] != '\0')) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 2));
		    }
		    tmp[1] = '\0';
		    written += 2;
		} else {
		    /*
		     * Start emphasis immediately if we are making the link
		     * non-current, or we are making it current but this is not
		     * the first or last character of the hightext.  -FM
		     */
		    if (flag != TRUE ||
			(offset > hoffset && data[itmp + 1] != '\0')) {
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			LYaddstr(tmp);
		    } else {
			LYmove(hLine, (offset + 1));
		    }
		    written++;
		}
		itmp++;
		/*
		 * Start emphasis after the first character if we are making
		 * the link current and this is not the last character.  -FM
		 */
		if (!TargetEmphasisON &&
		    data[itmp] != '\0') {
		    LYstartTargetEmphasis();
		    TargetEmphasisON = TRUE;
		}

		for (;
		     written < len && (tmp[0] = data[itmp]) != '\0';
		     itmp++) {
		    /*
		     * Print all the other target chars, except the last
		     * character if it is also the last character of hightext
		     * and we are making the link current.  -FM
		     */
		    utf_extra = utf8_length(utf_flag, data + itmp);
		    if (utf_extra) {
			LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
			itmp += (int) utf_extra;
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			tmp[1] = '\0';
			written += (int) (utf_extra + 1);
		    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
			/*
			 * For CJK strings, by Masanobu Kimura.
			 */
			tmp[1] = data[++itmp];
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			tmp[1] = '\0';
			written += 2;
		    } else {
			/*
			 * Make sure we don't restore emphasis to the last
			 * character of hightext if we are making the link
			 * current.  -FM
			 */
			if (flag == TRUE && data[(itmp + 1)] == '\0') {
			    LYstopTargetEmphasis();
			    TargetEmphasisON = FALSE;
			    LYGetYX(y, offset);
			    LYmove(hLine, (offset + 1));
			} else {
			    LYaddstr(tmp);
			}
			written++;
		    }
		}

		/*
		 * Stop the emphasis if we haven't already, then reset the
		 * offset to our current position in the line, and if that is
		 * beyond the link, or we are making the link current and it is
		 * the last character in the hightext, we are done.  -FM
		 */
		if (TargetEmphasisON) {
		    LYstopTargetEmphasis();
		    TargetEmphasisON = FALSE;
		}
		LYGetYX(y, offset);
		if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
		/*
		 * See if we have another hit that starts within the
		 * hightext.  -FM
		 */
		    && ((cp =
			 LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
								mydata,
								offset - Offset),
					     target,
					     utf_flag, YES,
					     &HitOffset,
					     &LenNeeded)) != NULL)
		    && (offset + LenNeeded) < LYcols
		/*
		 * If the hit starts after the end of the hightext, or we
		 * are making the link current and the hit starts at its
		 * last character, we are done.  -FM
		 */
		    && (HitOffset + offset) <
		    (hoffset + (flag == TRUE ? (hLen - 1) : hLen))) {
		    /*
		     * If the target extends beyond our buffer, emphasize
		     * everything in the hightext starting at this hit.
		     * Otherwise, set up the data and offsets, and loop back.
		     * -FM
		     */
		    if ((HitOffset + (offset + tLen)) >= (hoffset + hLen)) {
			offset = (HitOffset + offset);
			data = SKIP_GLYPHS(utf_flag, mydata, offset - hoffset);
			if (utf_flag) {
			    LYrefresh();
			}
			LYmove(hLine, offset);
			itmp = 0;
			written = 0;
			len = (int) strlen(data);

			/*
			 * Turn the emphasis back on.  -FM
			 */
			LYstartTargetEmphasis();
			TargetEmphasisON = TRUE;
			for (;
			     written < len && (tmp[0] = data[itmp]) != '\0';
			     itmp++) {
			    /*
			     * Print all the other target chars, except the
			     * last character if it is also the last character
			     * of hightext and we are making the link current.
			     * -FM
			     */
			    utf_extra = utf8_length(utf_flag, data + itmp);
			    if (utf_extra) {
				LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
				itmp += (int) utf_extra;
				/*
				 * Make sure we don't restore emphasis to the
				 * last character of hightext if we are making
				 * the link current.  -FM
				 */
				if (flag == TRUE && data[(itmp + 1)] == '\0') {
				    LYstopTargetEmphasis();
				    TargetEmphasisON = FALSE;
				    LYGetYX(y, offset);
				    LYmove(hLine, (offset + 1));
				} else {
				    LYaddstr(tmp);
				}
				tmp[1] = '\0';
				written += (int) (utf_extra + 1);
			    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
				/*
				 * For CJK strings, by Masanobu Kimura.
				 */
				tmp[1] = data[++itmp];
				/*
				 * Make sure we don't restore emphasis to the
				 * last character of hightext if we are making
				 * the link current.  -FM
				 */
				if (flag == TRUE && data[(itmp + 1)] == '\0') {
				    LYstopTargetEmphasis();
				    TargetEmphasisON = FALSE;
				} else {
				    LYaddstr(tmp);
				}
				tmp[1] = '\0';
				written += 2;
			    } else {
				/*
				 * Make sure we don't restore emphasis to the
				 * last character of hightext if we are making
				 * the link current.  -FM
				 */
				if (flag == TRUE && data[(itmp + 1)] == '\0') {
				    LYstopTargetEmphasis();
				    TargetEmphasisON = FALSE;
				} else {
				    LYaddstr(tmp);
				}
				written++;
			    }
			}
			/*
			 * Turn off the emphasis if we haven't already, and
			 * then we're done.  -FM
			 */
			if (TargetEmphasisON) {
			    LYstopTargetEmphasis();
			}
		    } else {
			mydata = cp;
			Offset = (offset + HitOffset);
			data = buffer;
			offset = hoffset;
			goto highlight_hit_within_hightext;
		    }
		}
	    }
	}
    }
  highlight_search_done:
    FREE(theData);
    return (BOOLEAN) TargetEmphasisON;
}
#endif /* SHOW_WHEREIS_TARGETS */

#ifdef USE_COLOR_STYLE
static int find_cached_style(int cur,
			     int flag)
{
    int s = s_alink;

#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
    if (textfields_need_activation
	&& links[cur].type == WWW_FORM_LINK_TYPE
	&& F_TEXTLIKE(links[cur].l_form->type))
	s = s_curedit;
#endif

    if (flag != TRUE) {
	int x;

	/*
	 * This is where we try to restore the original style when a link is
	 * unhighlighted.  The cached styles array saves the original style
	 * just for this case.  If it doesn't have a color change saved at just
	 * the right position, we look at preceding positions in the same line
	 * until we find one.
	 */
	if (ValidCachedStyle(LYP, LXP)) {
	    CTRACE2(TRACE_STYLE,
		    (tfp, "STYLE.highlight.off: cached style @(%d,%d): ",
		     LYP, LXP));
	    s = (int) GetCachedStyle(LYP, LXP);
	    if (s == 0) {
		for (x = LXP - 1; x >= 0; x--) {
		    s = (int) GetCachedStyle(LYP, x);
		    if (s != 0) {
			SetCachedStyle(LYP, LXP, (unsigned) s);
			CTRACE2(TRACE_STYLE,
				(tfp, "found %d, x_offset=%d.\n", s, x - LXP));
			break;
		    }
		}
		if (s == 0) {
		    CTRACE2(TRACE_STYLE, (tfp, "not found, assume <a>.\n"));
		    s = s_a;
		}
	    } else {
		CTRACE2(TRACE_STYLE, (tfp, "found %d.\n", s));
	    }
	} else {
	    CTRACE2(TRACE_STYLE,
		    (tfp, "STYLE.highlight.off: can't use cache.\n"));
	    s = s_a;
	}
    } else {
	CTRACE2(TRACE_STYLE, (tfp, "STYLE.highlight.on: @(%d,%d).\n", LYP, LXP));
    }
    return s;
}
#endif /* USE_COLOR_STYLE */

/*
 * Highlight (or unhighlight) a given link.
 */
void LYhighlight(int flag,
		 int cur,
		 const char *target)
{
    char buffer[MAX_LINE];
    int i;
    int hi_count;
    int hi_offset;
    int title_adjust = (no_title ? -TITLE_LINES : 0);
    char tmp[7];
    const char *hi_string;

#ifdef SHOW_WHEREIS_TARGETS
    BOOL TargetEmphasisON = FALSE;
    BOOL target1_drawn = NO;
#endif
    BOOL utf_flag = (BOOL) IS_UTF8_TTY;
    BOOL hl1_drawn = NO;

    tmp[0] = tmp[1] = tmp[2] = '\0';

    /*
     * Bugs in the history code might cause -1 to be sent for cur, which yields
     * a crash when LYStrNCpy() is called with a nonsense pointer.  As far as I
     * know, such bugs have been squashed, but if they should reappear, this
     * works around them.  -FM
     */
    if (cur < 0) {
	CTRACE((tfp, "LYhighlight cur %d (bug workaround)\n", cur));
	cur = 0;
    }

    CTRACE((tfp, "LYhighlight at(%2d,%2d) %s %d [%d]:%s\n",
	    links[cur].ly, links[cur].lx,
	    (flag
	     ? "on"
	     : "off"),
	    cur,
	    links[cur].anchor_number,
	    NONNULL(target)));

#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
    if (flag == FALSE)
	textinput_redrawn = FALSE;
#endif

    if (nlinks > 0) {
#ifdef USE_COLOR_STYLE
	if (flag == TRUE || links[cur].type == WWW_FORM_LINK_TYPE) {
	    LYmove(LYP + title_adjust, LXP);
	    LynxChangeStyle(find_cached_style(cur, flag), STACK_ON);
	}
#else
	if (links[cur].type == WWW_FORM_LINK_TYPE
	    || LYGetHiliteStr(cur, 0) == NULL) {
	    LYMoveToLink(cur, target, NULL,
			 flag, links[cur].inUnderline, utf_flag);
	    lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
	} else {
	    LYMoveToLink(cur, target, LYGetHiliteStr(cur, 0),
			 flag, links[cur].inUnderline, utf_flag);
	    hl1_drawn = YES;
#ifdef SHOW_WHEREIS_TARGETS
	    target1_drawn = YES;
#endif
	}
#endif

	if (links[cur].type == WWW_FORM_LINK_TYPE) {
	    int len;
	    int avail_space = (LYcolLimit - LXP) + (LYcolLimit * (LYlines - LYP));
	    const char *text = LYGetHiliteStr(cur, 0);

	    if (text == 0)
		text = "";

	    if (avail_space > links[cur].l_form->size)
		avail_space = links[cur].l_form->size;

	    len = (int) (LYmbcs_skip_cells(text, avail_space, utf_flag) - text);
	    LYwaddnstr(LYwin, text, (size_t) len);
	    while (len++ < avail_space)
		LYaddch('_');

#ifdef USE_COLOR_STYLE
	} else if (flag == FALSE) {
	    redraw_lines_of_link(cur);
	    CTRACE2(TRACE_STYLE,
		    (tfp, "STYLE.highlight.off: NOFIX branch @(%d,%d).\n",
		     LYP, LXP));
#endif
	} else if (!hl1_drawn) {
	    /*
	     * Copy into the buffer only what will fit within the width of the
	     * screen.
	     */
	    LYmbcsstrncpy(buffer,
			  NonNull(LYGetHiliteStr(cur, 0)),
			  (int) (sizeof(buffer) - 1),
			  (LYcolLimit - LXP),
			  utf_flag);
	    LYaddstr(buffer);
	}

	/*
	 * Display a second line as well.
	 */
	for (hi_count = 1;
	     (hi_string = LYGetHiliteStr(cur, hi_count)) != NULL
	     && LYP + hi_count <= display_lines;
	     ++hi_count) {
	    int row = LYP + hi_count + title_adjust;

	    hi_offset = LYGetHilitePos(cur, hi_count);
	    if (hi_offset < 0)
		continue;
	    lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
	    LYmove(row, hi_offset);

#ifdef USE_COLOR_STYLE
	    CTRACE2(TRACE_STYLE,
		    (tfp, "STYLE.highlight.line2: @(%d,%d), style=%d.\n",
		     row, hi_offset,
		     flag == TRUE ? s_alink : s_a));
	    LynxChangeStyle(flag == TRUE ? s_alink : s_a, ABS_ON);
#else
	    lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
#endif

	    for (i = 0; (tmp[0] = hi_string[i]) != '\0'
		 && (i + hi_offset) < LYcols; i++) {
		if (!IsSpecialAttrChar(hi_string[i])) {
		    /*
		     * For CJK strings, by Masanobu Kimura.
		     */
		    if (IS_CJK_TTY && is8bits(tmp[0])) {
			tmp[1] = hi_string[++i];
			LYaddstr(tmp);
			tmp[1] = '\0';
		    } else {
			LYaddstr(tmp);
		    }
		}
	    }
	}
	lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
#ifdef SHOW_WHEREIS_TARGETS
	for (hi_count = target1_drawn ? 1 : 0;
	     LYGetHiliteStr(cur, hi_count) != NULL;
	     hi_count++) {
	    TargetEmphasisON = show_whereis_targets(flag,
						    cur,
						    hi_count,
						    target,
						    TargetEmphasisON,
						    utf_flag);
	}

	if (!LYShowCursor)
	    /*
	     * Get cursor out of the way.
	     */
	    LYHideCursor();
	else
#endif /* SHOW_WHEREIS_TARGETS */
	    /*
	     * Never hide the cursor if there's no FANCY CURSES or SLANG.
	     */
	    LYmove(LYP + title_adjust, ((LXP > 0) ? (LXP - 1) : 0));

	if (flag)
	    LYrefresh();
    }
    return;
}

/*
 * free_and_clear will free a pointer if it is non-zero and then set it to
 * zero.
 */
void free_and_clear(char **pointer)
{
    if (*pointer) {
	FREE(*pointer);
	*pointer = 0;
    }
    return;
}

/*
 * Convert single or serial newlines to single spaces throughout a string
 * (ignore newlines if the preceding character is a space) and convert tabs to
 * single spaces.  Don't ignore any explicit tabs or spaces if the condense
 * argument is FALSE, otherwise, condense any serial spaces or tabs to one
 * space.  - FM
 */
void convert_to_spaces(char *string,
		       int condense)
{
    char *s = string;
    char *ns;
    BOOL last_is_space = FALSE;

    if (!s)
	return;

    s = LYSkipNonBlanks(s);
    ns = s;

    while (*s) {
	switch (*s) {
	case ' ':
	case '\t':
	    if (!(condense && last_is_space))
		*(ns++) = ' ';
	    last_is_space = TRUE;
	    break;

	case '\r':
	case '\n':
	    if (!last_is_space) {
		*(ns++) = ' ';
		last_is_space = TRUE;
	    }
	    break;

	default:
	    *(ns++) = *s;
	    last_is_space = FALSE;
	    break;
	}
	s++;
    }
    *ns = '\0';
    return;
}

/*
 * Strip trailing slashes from directory paths.
 */
char *strip_trailing_slash(char *dirname)
{
    int i;

    i = (int) strlen(dirname) - 1;
    while (i >= 0 && dirname[i] == '/')
	dirname[i--] = '\0';
    return (dirname);
}

/*
 * Remove most blanks, but restore one trailing blank to make prompts nicer.
 */
static void remove_most_blanks(char *buffer)
{
    int length = (int) strlen(buffer);
    BOOL trailing = (BOOL) ((length != 0) && (buffer[length - 1] == ' '));

    LYReduceBlanks(buffer);
    if (trailing)
	strcat(buffer, " ");
}

/*
 * Display (or hide) the status line.
 */
BOOLEAN mustshow = FALSE;

void statusline(const char *text)
{
    char buffer[MAX_LINE];
    unsigned char *temp = NULL;
    int max_length, len, i, j;
    int at_lineno;
    unsigned char k;
    char *p;
    char text_buff[MAX_LINE];

    if (text == NULL)
	return;

    /*
     * Don't print statusline messages if dumping to stdout.
     */
    if (dump_output_immediately)
	return;

    /*
     * Don't print statusline message if turned off.
     */
    if (mustshow != TRUE) {
	if (no_statusline == TRUE) {
	    return;
	}
    }
    mustshow = FALSE;

    /* "LYNXDOWNLOAD://Method=-1/File=%s/SugFile=%s%s\">Save to disk</a>\n" */
    LYStrNCpy(text_buff, text, sizeof(text_buff) - 1);
    p = StrChr(text_buff, '\n');
    if (p)
	*p = '\0';

    /*
     * Deal with any CJK escape sequences and Kanji if we have a CJK character
     * set selected, otherwise, strip any escapes.  Also, make sure text is not
     * longer than the statusline window.  - FM
     */
    max_length = (((LYcolLimit - 1) < (int) sizeof(buffer))
		  ? (LYcolLimit - 1)
		  : (int) sizeof(buffer) - 1);
    if ((text_buff[0] != '\0') &&
	(LYHaveCJKCharacterSet)) {
	/*
	 * Translate or filter any escape sequences.  - FM
	 */
	if ((temp = typecallocn(unsigned char, strlen(text_buff) + 1)) == NULL)
	      outofmem(__FILE__, "statusline");

	if (kanji_code == EUC) {
	    TO_EUC((const unsigned char *) text_buff, temp);
	} else if (kanji_code == SJIS) {
#ifdef KANJI_CODE_OVERRIDE
	    if (!LYRawMode || last_kcode == SJIS)
		strcpy(temp, text_buff);
	    else
		TO_SJIS((const unsigned char *) text_buff, temp);
#else
	    strcpy((char *) temp, text_buff);
#endif
	} else {
	    for (i = 0, j = 0; text_buff[i]; i++) {
		if (text_buff[i] != CH_ESC) {	/* S/390 -- gil -- 2119 */
		    temp[j++] = UCH(text_buff[i]);
		}
	    }
	    temp[j] = '\0';
	}

	/*
	 * Deal with any newlines or tabs in the string.  - FM
	 */
	remove_most_blanks((char *) temp);

	/*
	 * Handle the Kanji, making sure the text is not longer than the
	 * statusline window.  - FM
	 */
	for (i = 0, j = 0, len = 0, k = '\0';
	     temp[i] != '\0' && len < max_length; i++) {
	    if (k != '\0') {
		buffer[j++] = (char) k;
		buffer[j++] = (char) temp[i];
		k = '\0';
		len += 2;
	    } else if ((temp[i] & 0200) != 0) {
		k = temp[i];
	    } else {
		buffer[j++] = (char) temp[i];
		len++;
	    }
	}
	buffer[j] = '\0';
	FREE(temp);
    } else {
	/*
	 * Deal with any newlines or tabs in the string.  - FM
	 */
	remove_most_blanks(text_buff);
#ifdef WIDEC_CURSES
	len = (int) strlen(text_buff);
	if (len >= (int) (sizeof(buffer) - 1))
	    len = (int) (sizeof(buffer) - 1);
	LYStrNCpy(buffer, text_buff, len);
	/* FIXME: a binary search might be faster */
	while (len > 0 && LYstrExtent(buffer, len, len) > max_length)
	    buffer[--len] = '\0';
#else
	/*
	 * Strip any escapes, and shorten text if necessary.  Note that we
	 * don't deal with the possibility of UTF-8 characters in the string. 
	 * This is unlikely, but if strings with such characters are used in
	 * LYMessages_en.h, a compilation symbol of HAVE_UTF8_STATUSLINES could
	 * be added there, and code added here for determining the displayed
	 * string length, as we do above for CJK.  - FM
	 */
	for (i = 0, len = 0; text_buff[i] != '\0' && len < max_length; i++) {
	    if (text_buff[i] != CH_ESC) {	/* S/390 -- gil -- 2119 */
		buffer[len++] = text_buff[i];
	    }
	}
	buffer[len] = '\0';
#endif
    }

    /*
     * Move to the desired statusline window and output the text highlighted. 
     * - FM
     */
    if (LYStatusLine >= 0) {
	if (LYStatusLine < LYlines - 1) {
	    at_lineno = LYStatusLine;
	} else {
	    at_lineno = LYlines - 1;
	}
    } else if (user_mode == NOVICE_MODE) {
	at_lineno = LYlines - 3;
    } else {
	at_lineno = LYlines - 1;
    }
    LYmove(at_lineno, 0);
    LYclrtoeol();

    if (buffer[0] != '\0') {
	BOOLEAN has_CJK = FALSE;

	if (IS_CJK_TTY) {
	    for (i = 0; buffer[i] != '\0'; i++) {
		if (buffer[i] & 0x80) {
		    has_CJK = TRUE;
		    break;
		}
	    }
	}

	if (has_CJK
#ifdef HAVE_UTF8_STATUSLINES
	    || IS_UTF8_TTY
#endif
	    ) {
	    LYrefresh();
	}
#ifndef USE_COLOR_STYLE
	lynx_start_status_color();
	LYaddstr(buffer);
	lynx_stop_status_color();
#else
	/* draw the status bar in the STATUS style */
	{
	    int y, x;
	    int a = ((StrNCmp(buffer, ALERT_FORMAT, ALERT_PREFIX_LEN)
		      || !hashStyles[s_alert].used)
		     ? s_status
		     : s_alert);

	    LynxChangeStyle(a, STACK_ON);
	    LYaddstr(buffer);
	    wbkgdset(LYwin,
		     ((lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
		      ? (chtype) hashStyles[a].color
		      : A_NORMAL) | ' ');
	    LYGetYX(y, x);
	    (void) x;
	    if (y == at_lineno) {
		LYclrtoeol();
	    }
	    if (!(lynx_has_color && LYShowColor >= SHOW_COLOR_ON))
		wbkgdset(LYwin, A_NORMAL | ' ');
	    else if (s_normal != NOSTYLE)
		wbkgdset(LYwin, (chtype) (hashStyles[s_normal].color | ' '));
	    else
		wbkgdset(LYwin, (chtype) (displayStyles[DSTYLE_NORMAL].color | ' '));
	    LynxChangeStyle(a, STACK_OFF);
	}
#endif
    }
    LYrefresh();

    return;
}

static const char *novice_lines(int lineno)
{
    switch (lineno) {
    case 0:
	return NOVICE_LINE_TWO_A;
    case 1:
	return NOVICE_LINE_TWO_B;
    case 2:
	return NOVICE_LINE_TWO_C;
    default:
	return "";
    }
}

static int lineno = 0;

void toggle_novice_line(void)
{
    lineno++;
    if (*novice_lines(lineno) == '\0')
	lineno = 0;
    return;
}

void noviceline(int more_flag GCC_UNUSED)
{
    if (dump_output_immediately)
	return;

    LYmove(LYlines - 2, 0);
    LYclrtoeol();
    LYaddstr(NOVICE_LINE_ONE);

    LYParkCursor();
#if defined(DIRED_SUPPORT ) && defined(OK_OVERRIDE)
    if (lynx_edit_mode && !no_dired_support)
	LYaddstr(DIRED_NOVICELINE);
    else
#endif /* DIRED_SUPPORT && OK_OVERRIDE */

    if (LYUseNoviceLineTwo)
	LYaddstr(NOVICE_LINE_TWO);
    else
	LYaddstr(novice_lines(lineno));

    LYrefresh();
    return;
}

/*
 * If the standard input is not a tty, and Lynx is really reading from the
 * standard input, attempt to reopen it, pointing to a real tty.  Normally
 * this would happen if the user pipes data to Lynx and wants to run
 * interactively after that.
 *
 * Returns:
 *     1  if successfully reopened
 *    -1  if we cannot reopen
 *     0  if we do not have to reopen
 */
int LYReopenInput(void)
{
    int result = 0;
    int fd;

    if ((fd = fileno(stdin)) == 0
	&& !isatty(fd)
	&& LYConsoleInputFD(FALSE) == fd) {
	const char *term_name = NULL;
	int new_fd = -1;

#ifdef HAVE_TTYNAME
	if (isatty(fileno(stdout)) &&
	    (term_name = ttyname(fileno(stdout))) != NULL)
	    new_fd = open(term_name, O_RDONLY);

	if (new_fd == -1 &&
	    isatty(fileno(stderr)) &&
	    (term_name = ttyname(fileno(stderr))) != NULL)
	    new_fd = open(term_name, O_RDONLY);
#endif

#ifdef HAVE_CTERMID
	if (new_fd == -1 &&
	    (term_name = ctermid(NULL)) != NULL)
	    new_fd = open(term_name, O_RDONLY);
#endif

#ifdef TTY_DEVICE
	if (new_fd == -1)
	    new_fd = open(term_name = TTY_DEVICE, O_RDONLY);
#endif

	CTRACE((tfp, "LYReopenInput open(%s) returned %d.\n", term_name, new_fd));
	if (new_fd >= 0) {
	    FILE *frp;

	    close(new_fd);
	    frp = freopen(term_name, "r", stdin);
	    CTRACE((tfp,
		    "LYReopenInput freopen(%s,\"r\",stdin) returned %p, stdin is now %p with fd %d.\n",
		    term_name, (void *) frp, (void *) stdin, fileno(stdin)));
	    result = 1;
	} else {
	    result = -1;
	}
    }
    return result;
}

/*
 * Returns the file descriptor from which keyboard input is expected, or INVSOC
 * (-1) if not available.  If need_selectable is true, returns non-INVSOC fd
 * only if select() is possible - actually, currently only checks if fd is
 * connected to a tty.  - kw
 */
int LYConsoleInputFD(int need_selectable)
{
    int fd = INVSOC;

#ifdef USE_SLANG
    if (!LYCursesON)
	fd = fileno(stdin);
#if ((SLANG_VERSION >= 9919) && defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__))
    /* SLang_TT_Read_FD introduced in slang 0.99.19, from its changelog: 
     * SLang_TT_Read_FD variable is now available for unix.  This is the file
     * descriptor used by SLang_getkey.  */
    else
	fd = SLang_TT_Read_FD;
#endif /* SLANG_VERSION >= 9919 */
#else /* !USE_SLANG */
    fd = fileno(stdin);
#endif /* !USE_SLANG */

    if (need_selectable && fd != INVSOC) {
	if (isatty(fd)) {
	    return fd;
	} else {
	    return INVSOC;
	}
    }
    return fd;
}

static int fake_zap = 0;

void LYFakeZap(int set)
{
    if (set && fake_zap < 1) {
	CTRACE((tfp, "\r *** Set simulated 'Z'"));
	if (fake_zap)
	    CTRACE((tfp, ", %d pending", fake_zap));
	CTRACE((tfp, " ***\n"));
	fake_zap++;
    } else if (!set && fake_zap) {
	CTRACE((tfp, "\r *** Unset simulated 'Z'"));
	CTRACE((tfp, ", %d pending", fake_zap));
	CTRACE((tfp, " ***\n"));
	fake_zap = 0;
    }

}

static int DontCheck(void)
{
    static time_t last;
    time_t next;

    /** Curses or slang setup was not invoked **/
    if (dump_output_immediately)
	return (TRUE);

    if (LYHaveCmdScript())	/* we may be running from a script */
	return (TRUE);

    if (LYNoZapKey)
	return (TRUE);
    /*
     * Avoid checking interrupts more than one per second, since it is a slow
     * and expensive operation - TD
     */
#ifdef HAVE_GETTIMEOFDAY
#undef timezone			/* U/Win defines a conflicting macro */
    {
	struct timeval tv;

	gettimeofday(&tv, (struct timezone *) 0);
	next = (tv.tv_sec * 10);
	next += (tv.tv_usec / 100000L);		/* 0.1 seconds is a compromise */
    }
#else
    next = time((time_t *) 0);
#endif
    if (next == last)
	return (TRUE);

    last = next;
    return FALSE;
}

int HTCheckForInterrupt(void)
{
    int c;
    int cmd;

    if (fake_zap > 0) {
	fake_zap--;
	CTRACE((tfp, "\r *** Got simulated 'Z' ***\n"));
	CTRACE_FLUSH(tfp);
	CTRACE_SLEEP(AlertSecs);
	return ((int) TRUE);
    }

    /** Curses or slang setup was not invoked **/
    if (DontCheck())
	return ((int) FALSE);

#ifndef VMS			/* UNIX stuff: */

#if !defined(_WINDOWS) || defined(__MINGW32__)

    /*
     * First, check if there is a character.
     */
#ifdef USE_SLANG
    /** No keystroke was entered
	Note that this isn't taking possible SOCKSification
	and the socks_flag into account, and may fail on the
	slang library's select() when SOCKSified. - FM **/
#ifdef DJGPP_KEYHANDLER
    if (0 == _bios_keybrd(_NKEYBRD_READY))
	return (FALSE);
#else
    if (0 == SLang_input_pending(0))
	return (FALSE);
#endif /* DJGPP_KEYHANDLER */

#else /* Unix curses: */
    {
	struct timeval socket_timeout;
	int ret = 0;
	fd_set readfds;

	socket_timeout.tv_sec = 0;
	socket_timeout.tv_usec = 0;
	FD_ZERO(&readfds);
	FD_SET(0, &readfds);
#ifdef SOCKS
	if (socks_flag)
	    ret = Rselect(1, &readfds, NULL, NULL, &socket_timeout);
	else
#endif /* SOCKS */
	    ret = select(1, &readfds, NULL, NULL, &socket_timeout);

	/** Suspended? **/
	if ((ret == -1) && (SOCKET_ERRNO == EINTR))
	    return ((int) FALSE);

	/** No keystroke was entered? **/
	if (!FD_ISSET(0, &readfds))
	    return ((int) FALSE);
    }
#endif /* USE_SLANG */

#endif /* !_WINDOWS */

    /*
     * Now, read the character.
     */
#if defined(USE_CURSES_NODELAY)
    nodelay(LYwin, TRUE);
    c = LYgetch();
    nodelay(LYwin, FALSE);
#elif defined(USE_SLANG) && defined(_WINDOWS)
    if (!SLang_input_pending(0))
	return ((int) FALSE);
    c = LYgetch();
#else
    c = LYgetch();
#endif

#else /* VMS: */
    extern int typeahead(void);

    /** Control-C or Control-Y and a 'N'o reply to exit query **/
    if (HadVMSInterrupt) {
	HadVMSInterrupt = FALSE;
	return ((int) TRUE);
    }

    c = typeahead();

#endif /* !VMS */

    /*
     * 'c' contains whatever character we're able to read from keyboard
     */

    /** Keyboard 'Z' or 'z', or Control-G or Control-C **/
    if (LYCharIsINTERRUPT(c))
	return ((int) TRUE);

    /* There is a subset of mainloop() actions available at this stage:  no new
     * getfile() cycle is possible until the previous finished.  Currently we
     * have scrolling in partial mode, toggling of trace log, and pasting. 
     * User search now in progress...
     */
    cmd = (LKC_TO_LAC(keymap, c));
    switch (cmd) {
    case LYK_TRACE_TOGGLE:	/*  Toggle TRACE mode. */
	handle_LYK_TRACE_TOGGLE();
	break;
#ifdef CAN_CUT_AND_PASTE
    case LYK_TO_CLIPBOARD:{	/* ^S */
	    const char *s = LYDownLoadAddress();

	    if (!s || !*s || put_clip(s))
		HTInfoMsg(gettext("Copy to clipboard failed."));
	    else
		HTInfoMsg(gettext("Download document URL put to clipboard."));
	    break;
	}
#endif /* defined CAN_CUT_AND_PASTE */
    default:
#ifdef DISP_PARTIAL
	/* OK, we got several lines from new document and want to scroll... */
	if (display_partial && (NumOfLines_partial > 2)) {
	    BOOLEAN do_refresh;
	    int res;
	    int Newline_partial = LYGetNewline();

	    switch (cmd) {
	    case LYK_WHEREIS:	/* search within the document */
	    case LYK_NEXT:	/* search for the next occurrence in the document */
	    case LYK_PREV:	/* search for the previous occurrence in the document */
		handle_LYK_WHEREIS(cmd, &do_refresh);
		if (www_search_result != -1) {
		    Newline_partial = www_search_result;
		    www_search_result = -1;	/* reset */
		}
		break;

	    case LYK_FASTBACKW_LINK:
		if (Newline_partial <= (display_lines) + 1) {
		    Newline_partial -= display_lines;
		} else if ((res =
			    HTGetLinkOrFieldStart(-1,
						  &Newline_partial, NULL,
						  -1, TRUE)) == LINK_LINE_FOUND) {
		    Newline_partial++;
		} else if (res == LINK_DO_ARROWUP) {
		    Newline_partial -= display_lines;
		}
		break;
	    case LYK_FASTFORW_LINK:
		if (HText_canScrollDown()) {
		    /* This is not an exact science... - kw */
		    if (HTGetLinkOrFieldStart(HText_LinksInLines(HTMainText,
								 Newline_partial,
								 display_lines)
					      - 1,
					      &Newline_partial, NULL,
					      1, TRUE) == LINK_LINE_FOUND) {
			Newline_partial++;
		    }
		}
		break;
	    case LYK_PREV_PAGE:
		if (Newline_partial > 1)
		    Newline_partial -= display_lines;
		break;
	    case LYK_NEXT_PAGE:
		if (HText_canScrollDown())
		    Newline_partial += display_lines;
		break;
	    case LYK_UP_HALF:
		if (Newline_partial > 1)
		    Newline_partial -= (display_lines / 2);
		break;
	    case LYK_DOWN_HALF:
		if (HText_canScrollDown())
		    Newline_partial += (display_lines / 2);
		break;
	    case LYK_UP_TWO:
		if (Newline_partial > 1)
		    Newline_partial -= 2;
		break;
	    case LYK_DOWN_TWO:
		if (HText_canScrollDown())
		    Newline_partial += 2;
		break;
	    case LYK_HOME:
		if (Newline_partial > 1)
		    Newline_partial = 1;
		break;
	    case LYK_END:
		if (HText_canScrollDown())
		    Newline_partial = HText_getNumOfLines() - display_lines + 1;
		/* calculate for "current" bottom value */
		break;
	    case LYK_REFRESH:
		break;
	    default:
		/** Other or no keystrokes **/
		return ((int) FALSE);
	    }			/* end switch */
	    if (Newline_partial < 1)
		Newline_partial = 1;
	    if (LYMainLoop_pageDisplay(Newline_partial))
		NumOfLines_partial = HText_getNumOfLines();
	}
#endif /* DISP_PARTIAL */
	break;
    }				/* end switch */
    /** Other or no keystrokes **/
    return ((int) FALSE);
}

/*
 * Check if the given filename looks like it's an absolute pathname, i.e.,
 * references a directory.
 */
BOOLEAN LYisAbsPath(const char *path)
{
    BOOLEAN result = FALSE;

    if (non_empty(path)) {
#ifdef VMS
	result = TRUE;
#else
#if defined(USE_DOS_DRIVES)
	result = (BOOLEAN) (LYIsPathSep(path[0])
			    || (LYIsDosDrive(path)
				&& LYIsPathSep(path[2])));
#else
	result = (BOOLEAN) (LYIsPathSep(path[0]));
#endif /* USE_DOS_DRIVES */
#endif
    }
    return result;
}

/*
 * Check if the given filename is the root path, e.g., "/" on Unix.
 */
BOOLEAN LYisRootPath(const char *path)
{
#if defined(USE_DOS_DRIVES)
    if (strlen(path) == 3
	&& LYIsDosDrive(path)
	&& LYIsPathSep(path[2]))
	return TRUE;
#endif
    return (BOOL) ((strlen(path) == 1) && LYIsPathSep(path[0]));
}

/*
 * A file URL for a remote host is an obsolete ftp URL.
 * Return YES only if we're certain it's a local file.  - FM
 */
BOOLEAN LYisLocalFile(const char *filename)
{
    char *host = NULL;
    char *acc_method = NULL;
    char *cp;

    if (!filename)
	return NO;
    if (!(host = HTParse(filename, "", PARSE_HOST)))
	return NO;
    if (!*host) {
	FREE(host);
	return NO;
    }

    if ((cp = StrChr(host, ':')) != NULL)
	*cp = '\0';

    if ((acc_method = HTParse(filename, "", PARSE_ACCESS))) {
	if (0 == strcmp("file", acc_method) &&
	    (0 == strcmp(host, "localhost") ||
	     LYSameFilename(host, HTHostName()))) {
	    FREE(host);
	    FREE(acc_method);
	    return YES;
	}
    }

    FREE(host);
    FREE(acc_method);
    return NO;
}

/*
 * Utility for checking URLs with a host field.  Return YES only if we're
 * certain it's the local host.  - FM
 */
BOOLEAN LYisLocalHost(const char *filename)
{
    char *host = NULL;
    char *cp;

    if (!filename)
	return NO;
    if (!(host = HTParse(filename, "", PARSE_HOST)))
	return NO;
    if (!*host) {
	FREE(host);
	return NO;
    }

    if ((cp = StrChr(host, ':')) != NULL)
	*cp = '\0';

    if ((LYSameFilename(host, "localhost") ||
	 LYSameFilename(host, LYHostName) ||
	 LYSameFilename(host, HTHostName()))) {
	FREE(host);
	return YES;
    }

    FREE(host);
    return NO;
}

/*
 * Free an HTList that contains strings.
 */
void LYFreeStringList(HTList *list)
{
    if (list != NULL) {
	char *argument;
	HTList *cur = list;

	while (NULL != (argument = (char *) HTList_nextObject(cur))) {
	    FREE(argument);
	}
	HTList_delete(list);
    }
}

/*
 * Utility for freeing the list of local host aliases.  - FM
 */
void LYLocalhostAliases_free(void)
{
    LYFreeStringList(localhost_aliases);
    localhost_aliases = NULL;
}

/*
 * Utility for listing hosts to be treated as local aliases.  - FM
 */
void LYAddLocalhostAlias(char *alias)
{
    char *LocalAlias = NULL;

    if (!non_empty(alias))
	return;

    if (!localhost_aliases) {
	localhost_aliases = HTList_new();
#ifdef LY_FIND_LEAKS
	atexit(LYLocalhostAliases_free);
#endif
    }

    StrAllocCopy(LocalAlias, alias);
    HTList_addObject(localhost_aliases, LocalAlias);

    return;
}

/*
 * Utility for checking URLs with a host field.  Return YES only if we've
 * listed the host as a local alias.  - FM
 */
BOOLEAN LYisLocalAlias(const char *filename)
{
    char *host = NULL;
    char *alias;
    char *cp;
    HTList *cur = localhost_aliases;

    if (!cur || !filename)
	return NO;
    if (!(host = HTParse(filename, "", PARSE_HOST)))
	return NO;
    if (!(*host)) {
	FREE(host);
	return NO;
    }

    if ((cp = StrChr(host, ':')) != NULL)
	*cp = '\0';

    while (NULL != (alias = (char *) HTList_nextObject(cur))) {
	if (LYSameFilename(host, alias)) {
	    FREE(host);
	    return YES;
	}
    }

    FREE(host);
    return NO;
}

/*
 *  This function checks for a URL with an unknown scheme,
 *  but for which proxying has been set up, and if so,
 *  returns PROXY_URL_TYPE. - FM
 *
 *  If a colon is present but the string segment which
 *  precedes it is not being proxied, and we can be sure
 *  that what follows the colon is not a port field,
 *  it returns UNKNOWN_URL_TYPE.  Otherwise, it returns
 *  0 (not a URL). - FM
 */
UrlTypes LYCheckForProxyURL(char *filename)
{
    char *cp = filename;
    char *cp1;
    char *cp2 = NULL;

    /*
     * Don't crash on an empty argument.
     */
    if (isEmpty(cp))
	return (NOT_A_URL_TYPE);

    /* kill beginning spaces */
    cp = LYSkipBlanks(cp);

    /*
     * Check for a colon, and if present,
     * see if we have proxying set up.
     */
    if ((cp1 = StrChr((cp + 1), ':')) != NULL) {
	if ((cp2 = StrChr((cp + 1), '/')) != NULL && cp2 < cp1)
	    return (NOT_A_URL_TYPE);
	*cp1 = '\0';
	cp2 = NULL;
	StrAllocCopy(cp2, cp);
	*cp1 = ':';
	StrAllocCat(cp2, "_proxy");
	if (LYGetEnv(cp2) != NULL) {
	    FREE(cp2);
	    return (PROXY_URL_TYPE);
	}
	FREE(cp2);
#if defined (USE_DOS_DRIVES)
	if (LYIsDosDrive(cp))
	    return (NOT_A_URL_TYPE);
#endif
	cp1++;
	if (!*cp) {
	    return (NOT_A_URL_TYPE);
	} else if (isdigit(UCH(*cp1))) {
	    while (*cp1 && isdigit(UCH(*cp1)))
		cp1++;
	    if (*cp1 && !LYIsHtmlSep(*cp1))
		return (UNKNOWN_URL_TYPE);
	} else {
	    return (UNKNOWN_URL_TYPE);
	}
    }

    return (NOT_A_URL_TYPE);
}

/*
 * Compare a "type:" string, replacing it by the comparison-string if it
 * matches (and return true in that case).
 */
static BOOLEAN compare_type(char *tst,
			    const char *cmp,
			    size_t len)
{
    if (!strncasecomp(tst, cmp, (int) len)) {
	if (StrNCmp(tst, cmp, len)) {
	    size_t i;

	    for (i = 0; i < len; i++)
		tst[i] = cmp[i];
	}
	return TRUE;
    }
    return FALSE;
}
#define CompareType(tst,cmp,len) compare_type((tst),(cmp),(size_t)(len))

#define DoubleHtmlSep(s) (LYIsHtmlSep((s)[0]) && LYIsHtmlSep((s)[1]))
#define compare_two(tst,cmp,len,limit) \
	((len + 2) <= limit \
	&& DoubleHtmlSep(tst + len) \
	&& CompareType(tst, cmp, len))

/*
 * Must recognize a URL and return the type.  If recognized, based on a
 * case-insensitive analysis of the scheme field, ensures that the scheme field
 * has the expected case.
 *
 * Returns 0 (not a URL) for a NULL argument, one which lacks a colon.
 *
 * Chains to LYCheckForProxyURL() if a colon is present but the type is not
 * recognized.
 */
UrlTypes is_url(char *filename)
{
    char *cp = filename;
    char *cp1;
    UrlTypes result = NOT_A_URL_TYPE;
    int limit;

    /*
     * Don't crash on an empty argument.
     */
    if (isEmpty(cp))
	return (result);

    /*
     * Can't be a URL if it lacks a colon and if it starts with '[' it's
     * probably IPv6 address.
     */
    if (NULL == StrChr(cp, ':') || cp[0] == '[')
	return (result);

    /*
     * Kill beginning spaces.
     */
    cp = LYSkipBlanks(cp);

    /*
     * Can't be a URL if it starts with a slash.  So return immediately for
     * this common case, also to avoid false positives if there was a colon
     * later in the string.  Also can't be a URL if it starts with a colon.  -
     * KW
     */
    if (*cp == ':' || LYIsHtmlSep(*cp)) {
	result = NOT_A_URL_TYPE;

    } else {
	limit = (int) strlen(cp);
	switch (*cp) {
	case 'L':
	case 'l':
	    /*
	     * Lynx internal pages ("LYNXfoo:" or "lynxfoo:") start with 'l' or
	     * 'L', other URLs aren't.
	     */
	    if (CompareType(cp, STR_LYNXEXEC, LEN_LYNXEXEC)) {
		/*
		 * Special External Lynx type to handle execution of commands
		 * or scripts which require a pause to read the screen upon
		 * completion.
		 */
		result = LYNXEXEC_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXPROG, LEN_LYNXPROG)) {
		/*
		 * Special External Lynx type to handle execution of commands,
		 * scripts or programs which do not require a pause to read
		 * screen upon completion.
		 */
		result = LYNXPROG_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXCGI, LEN_LYNXCGI)) {
		result = LYNXCGI_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXPRINT, LEN_LYNXPRINT)) {
		result = LYNXPRINT_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)) {
		result = LYNXOPTIONS_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXCFG, LEN_LYNXCFG)) {
		result = LYNXCFG_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXMESSAGES, LEN_LYNXMESSAGES)) {
		result = LYNXMESSAGES_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXCFLAGS, LEN_LYNXCFLAGS)) {
		result = LYNXCOMPILE_OPTS_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXDOWNLOAD, LEN_LYNXDOWNLOAD)) {
		result = LYNXDOWNLOAD_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXDIRED, LEN_LYNXDIRED)) {
		result = LYNXDIRED_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXEDITMAP, LEN_LYNXEDITMAP)) {
		result = LYNXEDITMAP_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXHIST, LEN_LYNXHIST)) {
		result = LYNXHIST_URL_TYPE;

#ifdef USE_CACHEJAR
	    } else if (CompareType(cp, STR_LYNXCACHE, LEN_LYNXCACHE)) {
		result = LYNXCACHE_URL_TYPE;
#endif
	    } else if (CompareType(cp, STR_LYNXKEYMAP, LEN_LYNXKEYMAP)) {
		result = LYNXKEYMAP_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXIMGMAP, LEN_LYNXIMGMAP)) {
		/* force lower/uppercase of next part */
		(void) is_url(&cp[LEN_LYNXIMGMAP]);
		result = LYNXIMGMAP_URL_TYPE;

	    } else if (CompareType(cp, STR_LYNXCOOKIE, LEN_LYNXCOOKIE)) {
		result = LYNXCOOKIE_URL_TYPE;

	    }
	    break;
#ifndef DISABLE_NEWS
	    /*
	     * NEWSfoo:  schemes -
	     */
	case 'N':
	case 'n':
	    if (CompareType(cp, STR_NEWS_URL, LEN_NEWS_URL)) {
		result = NEWS_URL_TYPE;

	    } else if (CompareType(cp, STR_NNTP_URL, LEN_NNTP_URL)) {
		result = NNTP_URL_TYPE;

	    } else if (CompareType(cp, "newspost:", 9)) {
		/*
		 * Special Lynx type to handle news posts.
		 */
		result = NEWSPOST_URL_TYPE;

	    } else if (CompareType(cp, "newsreply:", 10)) {
		/*
		 * Special Lynx type to handle news replies (followups).
		 */
		result = NEWSREPLY_URL_TYPE;
	    }
	    break;

	    /*
	     * SNEWSfoo:  schemes -
	     */
	case 'S':
	case 's':
	    if (CompareType(cp, STR_SNEWS_URL, LEN_SNEWS_URL)) {
		result = SNEWS_URL_TYPE;

	    } else if (CompareType(cp, "snewspost:", 10)) {
		/*
		 * Special Lynx type to handle snews posts.
		 */
		result = NEWSPOST_URL_TYPE;

	    } else if (CompareType(cp, "snewsreply:", 11)) {
		/*
		 * Special Lynx type to handle snews replies (followups).
		 */
		result = NEWSREPLY_URL_TYPE;
	    }
	    break;
#endif
	case 'M':
	case 'm':
	    if (CompareType(cp, STR_MAILTO_URL, LEN_MAILTO_URL)) {
		result = MAILTO_URL_TYPE;
	    }
	    break;

	case 'F':
	case 'f':
	    if (CompareType(cp, STR_FILE_URL, LEN_FILE_URL)) {
		if (LYisLocalFile(cp)) {
		    result = FILE_URL_TYPE;
		} else if (DoubleHtmlSep(cp + LEN_FILE_URL)) {
		    result = FTP_URL_TYPE;
		}
	    }
#ifndef DISABLE_FTP
	    else if (compare_two(cp, STR_FTP_URL, LEN_FTP_URL, limit)) {
		result = FTP_URL_TYPE;
	    }
#endif
#ifndef DISABLE_FINGER
	    else if (compare_two(cp, STR_FINGER_URL, LEN_FINGER_URL, limit)) {
		result = FINGER_URL_TYPE;
	    }
#endif
	    break;

	case 'B':
	case 'b':
#ifndef DISABLE_BIBP
	    if (CompareType(cp, STR_BIBP_URL, LEN_BIBP_URL)) {
		result = BIBP_URL_TYPE;
	    }
#endif
	    break;

	case 'D':
	case 'd':
	    if (CompareType(cp, "data:", 5)) {
		result = DATA_URL_TYPE;
	    }
	    break;

	default:
	    if (limit >= 3
		&& ((cp1 = StrChr(cp + 3, ':')) == NULL
		    || !DoubleHtmlSep(cp1 + 1))) {
		/*
		 * If it doesn't contain "://", and it's not one of the the
		 * above, it can't be a URL with a scheme we know, so check if
		 * it's an unknown scheme for which proxying has been set up.
		 * - FM
		 */
		if (cp1 != NULL
		    && (cp1 - cp) > 1	/* exclude DOS-style device:/path */
		    && LYisAbsPath(cp1 + 1)) {
		    result = NCFTP_URL_TYPE;
		}

	    } else {
		switch (*cp) {
		case 'H':
		case 'h':
		    if (CompareType(cp, STR_HTTP_URL, LEN_HTTP_URL)) {
			result = HTTP_URL_TYPE;

		    } else if (CompareType(cp, STR_HTTPS_URL, LEN_HTTPS_URL)) {
			result = HTTPS_URL_TYPE;
		    }
		    break;

#ifndef DISABLE_GOPHER
		case 'G':
		case 'g':
		    if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) {
			if (strlen(cp) >= 11
			    && (cp1 = StrChr(cp + 11, '/')) != NULL) {

			    if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w')
				/* if this is a gopher html type */
				result = HTML_GOPHER_URL_TYPE;
			    else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')
				result = TELNET_GOPHER_URL_TYPE;
			    else if (*(cp1 + 1) == '7')
				result = INDEX_GOPHER_URL_TYPE;
			    else
				result = GOPHER_URL_TYPE;
			} else {
			    result = GOPHER_URL_TYPE;
			}
		    }
		    break;
#endif
		case 'W':
		case 'w':
		    if (CompareType(cp, STR_WAIS_URL, LEN_WAIS_URL)) {
			result = WAIS_URL_TYPE;
		    }
		    break;

		case 'T':
		case 't':
		    if (CompareType(cp, STR_TELNET_URL, LEN_TELNET_URL)) {
			result = TELNET_URL_TYPE;

		    } else if (CompareType(cp, STR_TN3270_URL, LEN_TN3270_URL)) {
			result = TN3270_URL_TYPE;
		    }
		    break;

		case 'R':
		case 'r':
		    if (CompareType(cp, STR_RLOGIN_URL, LEN_RLOGIN_URL)) {
			result = RLOGIN_URL_TYPE;
		    }
		    break;

		case 'C':
		case 'c':
		    if (CompareType(cp, STR_CSO_URL, LEN_CSO_URL)) {
			result = CSO_URL_TYPE;
		    }
		    break;

		case 'A':
		case 'a':
		    if (CompareType(cp, "afs:", 4)) {
			result = AFS_URL_TYPE;
		    }
		    break;

		case 'P':
		case 'p':
		    if (CompareType(cp, "prospero:", 9)) {
			result = PROSPERO_URL_TYPE;
		    }
		    break;
		}
	    }
	}
	/*
	 * Check if it is an unknown scheme for which proxying has been set up.
	 */
	if (result == NOT_A_URL_TYPE)
	    result = LYCheckForProxyURL(filename);
    }
    return result;
}

/*
 * Sometimes it is just expected that curses is on when an alert or other
 * statusline message needs to be shown and we are not just dumping
 * immediately.  Calling this will 'fix' it, but may not always be appropriate. 
 * - kw
 */
void LYFixCursesOn(const char *reason)
{
    if (dump_output_immediately || LYCursesON)
	return;
    if (reason) {
	CTRACE((tfp, "Forcing curses on to %s\n", reason));
    }
    start_curses();
}

/*
 * Most protocol modules called through HTLoad* expect that curses is on unless
 * dump_output_immediately is set, so that statusline messages can be shown. 
 * Some protocols expect the opposite, namely telnet and friends.  This
 * function should be called after the 'physical' URL for accessing addr has
 * been established.  It does the right thing to the degree that curses is
 * turned on for known problem cases.  In any normal circumstances this should
 * never apply, but proxying or rule substitution is not prevented for
 * telnet-like URLs, and this 'fix' avoids some crashes that can otherwise
 * occur.  - kw
 */
BOOLEAN LYFixCursesOnForAccess(const char *addr,
			       const char *physical)
{
    /*
     * If curses is off when maybe it shouldn't...
     */
    if (!dump_output_immediately && !LYCursesON && physical) {
	char *cp1;

	/*
	 * If requested resource wants to be accessed with curses off, and
	 * getfile() would indeed have turned curses off for it...
	 */
	if (strstr(addr, "://") != NULL &&
	    (isTELNET_URL(addr) ||
	     isRLOGIN_URL(addr) ||
	     isTN3270_URL(addr) ||
	     (!isGOPHER_URL(addr) &&
	      (cp1 = StrChr(addr + 11, '/')) != NULL &&
	      (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')))) {
	    /*
	     * If actual access that will be done is ok with curses off, then
	     * do nothing special, else force curses on.  - kw
	     */
	    if (!isTELNET_URL(physical) &&
		!isRLOGIN_URL(physical) &&
		!isTN3270_URL(physical)) {
		start_curses();
		HTAlert(gettext("Unexpected access protocol for this URL scheme."));
		return TRUE;
	    }
	}
    }
    return FALSE;
}

/*
 * Determine whether we allow HEAD and related flags for a URL.  - kw
 */
BOOLEAN LYCanDoHEAD(const char *address)
{
    char *temp0 = NULL;
    int isurl;

    if (!non_empty(address))
	return FALSE;
    if (!StrNCmp(address, "http", 4))
	return TRUE;
    /* Make copy for is_url() since caller may not care for case changes */
    StrAllocCopy(temp0, address);
    isurl = is_url(temp0);
    if (!isurl) {
	FREE(temp0);
	return FALSE;
    }
    if (isurl == LYNXCGI_URL_TYPE) {
	FREE(temp0);
#if defined(LYNXCGI_LINKS) && !defined(VMS)
	return TRUE;
#else
	return FALSE;
#endif
    }
    /*
     * The idea of the following is to allow HEAD for news URLs that identify
     * single articles, not those that identify ranges of articles or groups or
     * a list of groups.  - kw
     */
    if (isurl == NEWS_URL_TYPE || isurl == NNTP_URL_TYPE) {
	char *temp = HTParse(address, "", PARSE_PATH);
	char *cp = strrchr(temp, '/');

	if (StrChr((cp ? cp : temp), '@') != NULL) {
	    FREE(temp0);
	    FREE(temp);
	    return TRUE;
	}
	if (cp && isdigit(UCH(cp[1])) && StrChr(cp, '-') == NULL) {
	    FREE(temp0);
	    FREE(temp);
	    return TRUE;
	}
	FREE(temp);
    }
#define ALLOW_PROXY_HEAD
/* If defined, also allow head requests for URLs proxied through the "http" or
 * "lynxcgi" protocols, which understand HEAD.  Only the proxy environment
 * variables are checked, not the HTRules system.  - kw
 */
#ifdef ALLOW_PROXY_HEAD
    if (isurl != FILE_URL_TYPE) {
	char *acc_method = HTParse(temp0, "", PARSE_ACCESS);

	if (non_empty(acc_method)) {
	    char *proxy;

	    StrAllocCat(acc_method, "_proxy");
	    proxy = LYGetEnv(acc_method);
	    if (proxy && (isHTTP_URL(proxy) ||
			  isLYNXCGI(proxy)) &&
		!override_proxy(temp0)) {
		FREE(temp0);
		FREE(acc_method);
		return TRUE;
	    }
	}
	FREE(acc_method);
    }
#endif /* ALLOW_PROXY_HEAD */

    FREE(temp0);
    return FALSE;
}

/*
 * Close an input file.
 */
BOOLEAN LYCloseInput(FILE *fp)
{
    int result = FALSE;

    if (fp != 0) {
	int err = ferror(fp);
	LY_TEMP *p = FindTempfileByFP(fp);

	fclose(fp);
	if (p != 0) {
	    p->file = 0;
	}
	if (!err) {
	    result = TRUE;
	}
    }
    return (BOOLEAN) result;
}

/*
 * Close an output file, reporting any problems with writing to it.
 */
BOOLEAN LYCloseOutput(FILE *fp)
{
    int result = FALSE;

    if (fp != 0) {
	int err = ferror(fp);
	LY_TEMP *p = FindTempfileByFP(fp);

	fclose(fp);
	if (p != 0) {
	    p->file = 0;
	}
	if (!err) {
	    result = TRUE;
	}
    }
    if (!result) {
	HTAlert(CANNOT_WRITE_TO_FILE);
    }
    return (BOOLEAN) result;
}

/*
 * Test if we'll be able to write a file.  If not, warn the user.
 */
BOOLEAN LYCanWriteFile(const char *filename)
{
    BOOLEAN result = FALSE;

    if (LYCloseOutput(fopen(filename, "w"))) {
	if (remove(filename) == 0) {
	    result = TRUE;
	}
    } else {
	_statusline(NEW_FILENAME_PROMPT);
    }
    return result;
}

/*
 * Test if we'll be able to read a file.
 */
BOOLEAN LYCanReadFile(const char *filename)
{
    FILE *fp;
    BOOLEAN result = FALSE;

    if (non_empty(filename)) {
	if ((fp = fopen(filename, "r")) != 0) {
	    result = LYCloseInput(fp);
	}
    }
    return result;
}

char *LYFindConfigFile(const char *nominal, const char *dftfile)
{
    char *result = 0;
    char *path = 0;
    char *head = 0;
    char *leaf;
    char *item;

    if (non_empty(nominal)) {
	StrAllocCopy(result, nominal);

	/*
	 * Look for it in as-is - first expanding any tilde.
	 */
	LYTildeExpand(&result, TRUE);
	if (!LYCanReadFile(result)) {
	    const char *cfg_path;
	    char *list = 0;
	    BOOLEAN found = FALSE;

	    /*
	     * Now try in the config-path.
	     */
	    if ((cfg_path = LYGetEnv("LYNX_CFG_PATH")) == NULL)
		cfg_path = LYNX_CFG_PATH;

	    StrAllocCopy(list, cfg_path);
	    path = list;
	    while ((item = LYstrsep(&path, PATH_SEPARATOR)) != 0) {
		if (isEmpty(item))
		    continue;
		FREE(result);
		HTSprintf0(&result, "%s%s%s", item, FILE_SEPARATOR, nominal);
		LYTildeExpand(&result, TRUE);
		if (LYCanReadFile(result)) {
		    found = TRUE;
		    break;
		}
	    }
	    FREE(list);

	    if (!found) {
		/*
		 * If not found, try finding it in the same directory as the
		 * compiled-in location of the default file.
		 */
		StrAllocCopy(head, dftfile);
		if (strcmp(nominal, dftfile) &&
		    (leaf = LYPathLeaf(head)) != head) {

		    head[leaf - head] = '\0';
		    StrAllocCopy(result, head);
		    StrAllocCat(result, nominal);

		    if (!LYCanReadFile(result)) {
			FREE(result);
		    }
		}
#ifdef USE_PROGRAM_DIR
		else {
		    /*
		     * Finally, try in the same directory as the executable.
		     */
		    StrAllocCopy(result, program_dir);
		    LYAddPathSep(&result);
		    StrAllocCat(result, nominal);
		    LYTildeExpand(&result, TRUE);
		    if (!LYCanReadFile(result)) {
			FREE(result);
		    }
		}
#endif
	    }
	}

    }
    FREE(head);
    return result;
}

/*
 * Remove backslashes from any string.
 */
void remove_backslashes(char *buf)
{
    char *cp;

    for (cp = buf; *cp != '\0'; cp++) {

	if (*cp != '\\') {	/* don't print slashes */
	    *buf = *cp;
	    buf++;
	} else if (*cp == '\\' &&	/* print one slash if there */
		   *(cp + 1) == '\\') {		/* are two in a row         */
	    *buf = *cp;
	    buf++;
	}
    }
    *buf = '\0';
    return;
}

/*
 * Checks to see if the current process is attached via a terminal in the local
 * domain.
 */
BOOLEAN inlocaldomain(void)
{
    BOOLEAN result = TRUE;

#ifdef HAVE_UTMP
    int n;
    FILE *fp;
    struct utmp me;
    char *cp, *mytty = NULL;

    if ((cp = ttyname(0))) {
	mytty = cp;
	if (!strncmp(mytty, "/dev/", 5)) {
	    mytty += 5;		/* pty's can be like "pts/0" in utmp */
	} else {
	    if ((mytty = LYLastPathSep(cp)) != 0)
		++mytty;
	}
    }

    result = FALSE;
    if (mytty && (fp = fopen(UTMP_FILE, "r")) != NULL) {
	size_t ulen = strlen(mytty);

	if (ulen > sizeof(me.ut_line))
	    ulen = sizeof(me.ut_line);
	do {
	    n = (int) fread((char *) &me, sizeof(struct utmp), (size_t) 1, fp);

	    if (n <= 0)
		break;
	} while (memcmp(me.ut_line, mytty, ulen));
	(void) LYCloseInput(fp);

	if (n > 0) {
	    for (ulen = 0; ulen < sizeof(me.ut_host); ++ulen) {
		if (me.ut_host[ulen] == '\0')
		    break;
	    }
	    if (ulen > strlen(LYLocalDomain) &&
		!memcmp(LYLocalDomain,
			me.ut_host + ulen - strlen(LYLocalDomain),
			strlen(LYLocalDomain))) {
		result = TRUE;
	    }
#ifdef LINUX
	    /* Linux fix to check for local user. J.Cullen 11Jul94              */
	    else if (ulen == 0) {
		result = TRUE;
	    }
#endif /* LINUX */
	}

    } else {
	CTRACE((tfp,
		"Could not get ttyname (returned %s) or open UTMP file %s\n",
		NONNULL(cp), UTMP_FILE));
    }
#else
    CTRACE((tfp, "LYUtils: inlocaldomain() not supported.\n"));
#endif /* HAVE_UTMP */
    return (result);
}

#ifdef HAVE_SIGACTION
/*
 * An extended alternative for calling signal(), sets some flags for signal
 * handler as we want them if that functionality is available.  (We don't
 * return anything from this function since the return value would currently be
 * ignored anyway.) - kw
 */
void LYExtSignal(int sig,
		 LYSigHandlerFunc_t *handler)
{
#ifdef SIGWINCH
    /* add more cases to if(condition) if required... */
    if (sig == SIGWINCH && LYNonRestartingSIGWINCH) {
	struct sigaction act;

	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(sig, &act, NULL);
    } else
#endif /* defined(SIGWINCH) */
	signal(sig, handler);
}
#endif /* HAVE_SIGACTION */

#if defined(SIGTSTP) && !defined(USE_SLANG)
#ifdef HAVE_SIGACTION
/*
 * For switching a signal's handling between SIG_DFL and something (possibly)
 * different that may have been set up by lynx code or e.g. by curses library. 
 * Uses sigaction to preserve / restore as much state as possible.
 *
 * Second arg is where to save or restore from.
 *
 * Third arg to_dfl specifies what to do:
 *	1	Save current state in where, set handling to SIG_DFL
 *	0	Restore current state to previously saved one in where
 *
 * Currently only used for SIGTSTP without SLANG, to prevent (n)curses signal
 * handler from running while lynx is waiting in system() for an interactive
 * command like an editor.  - kw
 */
static BOOLEAN LYToggleSigDfl(int sig,
			      struct sigaction *where,
			      int to_dfl)
{
    int rv = -1;
    struct sigaction oact;

    if (to_dfl == 1) {
	rv = sigaction(sig, NULL, &oact);
	if (rv == 0) {
	    if (oact.sa_handler != SIG_DFL) {
		oact.sa_handler = SIG_DFL;
		rv = sigaction(sig, &oact, where);
	    } else if (where) {
		memcpy(where, &oact, sizeof(oact));
		rv = 0;
	    }
	}
    } else {
	rv = sigaction(sig, where, NULL);
    }
    if (rv != 0) {
	CTRACE((tfp, "Error in LYToggleSigDfl: %s\n", LYStrerror(errno)));
	return FALSE;
    } else
	return TRUE;
}
#endif /* HAVE_SIGACTION */
#endif /* SIGTSTP && !USE_SLANG */

/**************
 * This bit of code catches window size change signals
 */

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

/* For systems that have both, but both can't be included, duh (or neither) */
/* FIXME: this whole chunk may be redundant */
#ifdef TERMIO_AND_CURSES
# ifdef TERMIO_AND_TERMIOS
#  include <termio.h>
# else
#  ifdef HAVE_TERMIOS_H
#   include <termios.h>
#  else
#   ifdef HAVE_TERMIO_H
#    include <termio.h>
#   endif /* HAVE_TERMIO_H */
#  endif /* HAVE_TERMIOS_H */
# endif	/* TERMIO_AND_TERMIOS */
#endif /* TERMIO_AND_CURSES */

void LYGetScreenSize(int sig GCC_UNUSED)
{
    int old_lines = LYlines;
    int old_cols = LYcols;

#ifdef USE_SLANG
#if defined(VMS) || defined(UNIX)
    SLtt_get_screen_size();
#endif /* VMS || UNIX */
    LYlines = SLtt_Screen_Rows;
    LYcols = SLtt_Screen_Cols;
    if (sig == 0)
	/* If called from start_curses(), no need to record size-changed */
	return;
#else /* Curses: */
#ifdef HAVE_SIZECHANGE
#if defined(TIOCGSIZE)
    struct ttysize win;

    if (ioctl(0, TIOCGSIZE, &win) == 0) {
	if (win.ts_lines != 0) {
	    LYlines = win.ts_lines;
	}
	if (win.ts_cols != 0) {
	    LYcols = win.ts_cols;
	}
    }
#elif defined(TIOCGWINSZ)
    struct winsize win;

    if (ioctl(0, (long) TIOCGWINSZ, &win) == 0) {
	if (win.ws_row != 0) {
	    LYlines = win.ws_row;
	}
	if (win.ws_col != 0) {
	    LYcols = win.ws_col;
	}
    }
#elif !defined(PDCURSES)
#error inconsistent settings for TIOCGSIZE/TIOCGWINSZ
#endif /* TIOCGSIZE/TIOCGWINSZ */
#endif /* HAVE_SIZECHANGE */

#ifdef __EMX__
    {
	int scrsize[2];

	_scrsize(scrsize);
	LYcols = scrsize[0];
	LYlines = scrsize[1];
    }
#endif

    if (LYlines <= 0)
	LYlines = DFT_ROWS;
    if (LYcols <= 0)
	LYcols = DFT_COLS;
#endif /* USE_SLANG */

    /*
     * Check if the screen size has actually changed.  - AJL
     */
    if (LYlines != old_lines || LYcols != old_cols) {
	recent_sizechange = TRUE;
	CTRACE((tfp, "Window size changed from (%d,%d) to (%d,%d)\n",
		old_lines, old_cols, LYlines, LYcols));
#if defined(CAN_SWITCH_DISPLAY_CHARSET) && defined(CAN_AUTODETECT_DISPLAY_CHARSET)
	/* May need to reload the font due to different char-box size */
	if (current_char_set != auto_display_charset)
	    Switch_Display_Charset(current_char_set, SWITCH_DISPLAY_CHARSET_RESIZE);
#endif
    }
}

/*
 * curses/slang functions call this to start catching SIGWINCH when sig==0,
 * and continue catching when sig!=0.
 */
void size_change(int sig GCC_UNUSED)
{
#ifdef SIGWINCH
    if (sig)
	size_is_changed = TRUE;
    LYExtSignal(SIGWINCH, size_change);
#endif /* SIGWINCH */
}

/*
 * Utility for freeing the list of previous suggested filenames.  - FM
 */
void HTSugFilenames_free(void)
{
    LYFreeStringList(sug_filenames);
    sug_filenames = NULL;
}

/*
 * Utility for listing suggested filenames, making any repeated filenames the
 * most current in the list.  - FM
 */
void HTAddSugFilename(char *fname)
{
    char *tmp = NULL;
    char *old;
    HTList *cur;

    if (!non_empty(fname))
	return;

    StrAllocCopy(tmp, fname);

    if (!sug_filenames) {
	sug_filenames = HTList_new();
#ifdef LY_FIND_LEAKS
	atexit(HTSugFilenames_free);
#endif
	HTList_addObject(sug_filenames, tmp);
	return;
    }

    cur = sug_filenames;
    while (NULL != (old = (char *) HTList_nextObject(cur))) {
	if (!strcmp(old, tmp)) {
	    HTList_removeObject(sug_filenames, old);
	    FREE(old);
	    break;
	}
    }
    HTList_addObject(sug_filenames, tmp);

    return;
}

/*
 * CHANGE_SUG_FILENAME -- Foteos Macrides 29-Dec-1993 Upgraded for use with
 * Lynx2.2 - FM 17-Jan-1994
 */
void change_sug_filename(char *fname)
{
    const char *cp2;
    char *temp = 0, *cp, *cp1, *end;

#ifdef VMS
    char *dot;
    int j, k;
#endif /* VMS */

    /*
     * Establish the current end of fname.
     */
    end = fname + strlen(fname);

    /*
     * Unescape fname.
     */
    HTUnEscape(fname);

    /*
     * Rename any temporary files.
     */
    cp2 = wwwName(lynx_temp_space);
    if (LYIsHtmlSep(*cp2)) {
	HTSprintf0(&temp, "file://localhost%s" PID_FMT, cp2, GETPID());
    } else {
	HTSprintf0(&temp, "file://localhost/%s" PID_FMT, cp2, GETPID());
    }
    if (!StrNCmp(fname, temp, strlen(temp))) {
	if ((cp = strrchr(fname, '.')) != 0) {
	    if (strlen(cp) > (strlen(temp) - 4))
		cp = NULL;
	}
	StrAllocCopy(temp, NonNull(cp));
	sprintf(fname, "temp%.*s", LY_MAXPATH - 10, temp);
    }
    FREE(temp);

    if (fname[strlen(fname) - 1] == '/')
	/*
	 * Hmm...  we have a directory name.  It is annoying to see a
	 * scheme+host+path name as a suggested one, let's remove the
	 * last_slash and go ahead like we have a file name.  - LP
	 */
	fname[strlen(fname) - 1] = '\0';

    /*
     * Remove everything up the the last_slash if there is one.
     */
    if ((cp = strrchr(fname, '/')) != NULL && strlen(cp) > 1) {
	cp1 = fname;
	/*
	 * Go past the slash.
	 */
	cp++;
	for (; *cp != '\0'; cp++, cp1++) {
	    *cp1 = *cp;
	}
	*cp1 = '\0';
    }
#ifdef _WINDOWS			/* 1998/05/05 (Tue) 10:08:05 */
    if ((cp = strrchr(fname, '=')) != NULL && strlen(cp) > 1) {
	cp1 = fname;
	/*
	 * Go past the '='.
	 */
	cp++;
	for (; *cp != '\0'; cp++, cp1++) {
	    *cp1 = *cp;
	}
	*cp1 = '\0';
    }
#endif

    /*
     * Trim off date-size suffix, if present.
     */
    if ((*(end - 1) == ']') && ((cp = strrchr(fname, '[')) != NULL) &&
	(cp > fname) && *(--cp) == ' ') {
	while (*cp == ' ') {
	    *(cp--) = '\0';
	}
    }
#ifdef VMS
    /*
     * Trim off VMS device and/or directory specs, if present.
     */
    if ((cp = StrChr(fname, '[')) != NULL &&
	(cp1 = strrchr(cp, ']')) != NULL && strlen(cp1) > 1) {
	cp1++;
	for (cp = fname; *cp1 != '\0'; cp1++) {
	    *(cp++) = *cp1;
	}
	*cp = '\0';
    }
    /*
     * Replace illegal or problem characters.
     */
    dot = fname + strlen(fname);
    for (cp = fname; cp < dot; cp++) {
	/*
	 * Replace with underscores.
	 */
	if (*cp == ' ' || *cp == '/' || *cp == ':' ||
	    *cp == '[' || *cp == ']' || *cp == '&') {
	    *cp = '_';
	    /*
	     * Replace with dashes.
	     */
	} else if (*cp == '!' || *cp == '?' || *cp == '\'' ||
		   *cp == ',' || *cp == ':' || *cp == '"' ||
		   *cp == '+' || *cp == '@' || *cp == '\\' ||
		   *cp == '(' || *cp == ')' || *cp == '=' ||
		   *cp == '<' || *cp == '>' || *cp == '#' ||
		   *cp == '%' || *cp == '*' || *cp == '`' ||
		   *cp == '~' || *cp == '^' || *cp == '|' ||
		   *cp < ' ' || (UCH(*cp)) > 126) {
	    *cp = '-';
	}
    }

    /*
     * Collapse any serial underscores.
     */
    cp = fname + 1;
    j = 0;
    while (cp < dot) {
	if (fname[j] == '_' && *cp == '_') {
	    cp++;
	} else {
	    fname[++j] = *cp++;
	}
    }
    fname[++j] = '\0';

    /*
     * Collapse any serial dashes.
     */
    dot = fname + (strlen(fname));
    cp = fname + 1;
    j = 0;
    while (cp < dot) {
	if (fname[j] == '-' && *cp == '-') {
	    cp++;
	} else {
	    fname[++j] = *cp++;
	}
    }
    fname[++j] = '\0';

    /*
     * Trim any trailing or leading underscores or dashes.
     */
    cp = fname + (strlen(fname)) - 1;
    while (*cp == '_' || *cp == '-') {
	*cp-- = '\0';
    }
    if (fname[0] == '_' || fname[0] == '-') {
	dot = fname + (strlen(fname));
	cp = fname;
	while ((*cp == '_' || *cp == '-') && cp < dot) {
	    cp++;
	}
	j = 0;
	while (cp < dot) {
	    fname[j++] = *cp++;
	}
	fname[j] = '\0';
    }

    /*
     * Replace all but the last period with _'s, or second to last if last is
     * followed by a terminal Z or z, or GZ or gz,
     * e.g., convert foo.tar.Z to foo.tar_Z
     * or, convert foo.tar.gz to foo.tar-gz
     */
    j = strlen(fname) - 1;
    if ((dot = strrchr(fname, '.')) != NULL) {
	if (TOUPPER(fname[j]) == 'Z') {
	    if ((fname[j - 1] == '.') &&
		(((cp = StrChr(fname, '.')) != NULL) && cp < dot)) {
		*dot = '_';
		dot = strrchr(fname, '.');
	    } else if (((TOUPPER(fname[j - 1]) == 'G') &&
			fname[j - 2] == '.') &&
		       (((cp = StrChr(fname, '.')) != NULL) && cp < dot)) {
		*dot = '-';
		dot = strrchr(fname, '.');
	    }
	}
	cp = fname;
	while ((cp = StrChr(cp, '.')) != NULL && cp < dot) {
	    *cp = '_';
	}

	/*
	 * But if the root is > 39 characters, move the period appropriately to
	 * the left.
	 */
	while (dot - fname > 39) {
	    *dot = '\0';
	    if ((cp = strrchr(fname, '_')) != NULL) {
		*cp = '.';
		*dot = '_';
	    } else if ((cp = strrchr(fname, '-')) != NULL) {
		*cp = '.';
		*dot = '_';
	    } else if (*(dot + 1) == '\0') {
		j = strlen(fname);
		while (j > 39) {
		    fname[j] = fname[j - 1];
		    j--;
		}
		fname[j] = '.';
	    } else {
		*dot = '.';
		j = 39;
		k = 0;
		while (dot[k] != '\0') {
		    fname[j++] = dot[k++];
		}
		fname[j] = '\0';
	    }
	    dot = strrchr(fname, '.');
	}

	/*
	 * Make sure the extension is < 40 characters.
	 */
	if ((fname + strlen(fname) - dot) > 39) {
	    *(dot + 40) = '\0';
	}

	/*
	 * Trim trailing dashes or underscores.
	 */
	j = (strlen(fname) - 1);
	while (fname[j] == '_' || fname[j] == '-') {
	    fname[j--] = '\0';
	}
    } else {
	/*
	 * No period, so put one on the end, or after the 39th character,
	 * trimming trailing dashes or underscores.
	 */
	if (strlen(fname) > 39) {
	    fname[39] = '\0';
	}
	j = (strlen(fname) - 1);
	while ((fname[j] == '_') || (fname[j] == '-')) {
	    j--;
	}
	fname[++j] = '.';
	fname[++j] = '\0';
    }

#else /* Not VMS (UNIX): */

    /*
     * Replace problem characters.
     */
    for (cp = fname; *cp != '\0'; cp++) {
	switch (*cp) {
	case '\'':
	case '"':
	case '/':
	case ' ':
	    *cp = '-';
	}
    }
#endif /* VMS (UNIX) */

    /*
     * Make sure the rest of the original string in nulled.
     */
    cp = fname + strlen(fname);
    while (cp < end) {
	*cp++ = '\0';
    }

    return;
}

/*
 * Construct a temporary-filename.  Assumes result is LY_MAXPATH chars long.
 */
static int fmt_tempname(char *result,
			const char *prefix,
			const char *suffix)
{
    int code;

#ifdef HAVE_RAND_TEMPNAME
#define SIZE_TEMPNAME ((MAX_TEMPNAME / BITS_PER_CHAR) + 1)
    static BOOL first = TRUE;
    static int names_used = 0;
    static unsigned char used_tempname[SIZE_TEMPNAME];
    unsigned offset, mask;
#endif
    static unsigned counter;
    char leaf[LY_MAXPATH];

    if (prefix == 0)
	prefix = "";
    if (suffix == 0)
	suffix = "";
    /*
     * Prefer a random value rather than a counter.
     */
#ifdef HAVE_RAND_TEMPNAME
    if (first) {
	lynx_srand((unsigned) ((long) time((time_t *) NULL) + (long) result));
	first = FALSE;
    }

    /* We don't really need all of the bits from rand().  The high-order bits
     * are the more-random portion in any case, but limiting the width of the
     * generated name is done partly to avoid problems on systems that may not
     * support long filenames.
     */
    counter = MAX_TEMPNAME;
    if (names_used < MAX_TEMPNAME) {
	long get_rand = (long) lynx_rand();
	long max_rand = (long) LYNX_RAND_MAX;

	counter = (unsigned) (((float) MAX_TEMPNAME * (float) get_rand) /
			      (float) max_rand + 1);
	/*
	 * Avoid reusing a temporary name, since there are places in the code
	 * which can refer to a temporary filename even after it has been
	 * closed and removed from the filesystem.
	 */
	do {
	    counter %= MAX_TEMPNAME;
	    offset = counter / BITS_PER_CHAR;
	    mask = (unsigned) (1 << (counter % BITS_PER_CHAR));
	    if ((used_tempname[offset] & mask) == 0) {
		names_used++;
		used_tempname[offset] |= UCH(mask);
		break;
	    }
	} while ((used_tempname[offset] & mask) == 0);
    }
    if (names_used >= MAX_TEMPNAME)
	HTAlert(gettext("Too many tempfiles"));
#else
    counter++;
#endif

#ifdef FNAMES_8_3
    /*
     * The 'lynx_temp_space' string ends with a '/' or '\\', so we only have to
     * limit the length of the leaf.  As received (e.g., from HTCompressed),
     * the suffix may contain more than a ".htm", e.g., "-txt.gz", so we trim
     * off from the filename portion to make room.
     */
    sprintf(leaf, PID_FMT PID_FMT, counter, GETPID());
    if (strlen(leaf) > 8)
	leaf[8] = 0;
    if (strlen(suffix) > 4 || *suffix != '.') {
	const char *tail = StrChr(suffix, '.');

	if (tail == 0)
	    tail = suffix + strlen(suffix);
	if (8 - (tail - suffix) >= 0)
	    leaf[8 - (tail - suffix)] = 0;
    }
    strcat(leaf, suffix);
#else
    sprintf(leaf, "L" PID_FMT "-%uTMP%s", GETPID(), counter, suffix);
#endif
    /*
     * Someone could have configured the temporary pathname to be too long.
     */
    if ((strlen(prefix) + strlen(leaf)) < LY_MAXPATH) {
	sprintf(result, "%s%s", prefix, leaf);
	code = TRUE;
    } else {
	sprintf(result, "%.*s", LY_MAXPATH - 1, leaf);
	code = FALSE;
    }
    CTRACE((tfp, "-> '%s'\n", result));
    return (code);
}

/*
 * Convert 4, 6, 2, 8 to left, right, down, up, etc.
 */
int number2arrows(int number)
{
    switch (number) {
    case '1':
	number = END_KEY;
	break;
    case '2':
	number = DNARROW_KEY;
	break;
    case '3':
	number = PGDOWN_KEY;
	break;
    case '4':
	number = LTARROW_KEY;
	break;
    case '5':
	number = DO_NOTHING;
	break;
    case '6':
	number = RTARROW_KEY;
	break;
    case '7':
	number = HOME_KEY;
	break;
    case '8':
	number = UPARROW_KEY;
	break;
    case '9':
	number = PGUP_KEY;
	break;
    }

    return (number);
}

/*
 * parse_restrictions takes a string of comma-separated restrictions and sets
 * the corresponding flags to restrict the facilities available.
 */
/* The first two are special:  we want to record whether "default" or "all"
 * restrictions were applied, in addition to the detailed effects of those
 * options.  - kw
 */
/* skip the special flags when processing "all" and "default": */
#define N_SPECIAL_RESTRICT_OPTIONS 2
/* *INDENT-OFF* */
static const struct {
    const char *name;
    BOOLEAN *flag;
    BOOLEAN can;
} restrictions[] = {
    { "default",	&had_restrictions_default, TRUE },
    { "all",		&had_restrictions_all,	TRUE },
    { "inside_telnet",	&no_inside_telnet,	CAN_ANONYMOUS_INSIDE_DOMAIN_TELNET },
    { "outside_telnet",	&no_outside_telnet,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_TELNET },
    { "telnet_port",	&no_telnet_port,	CAN_ANONYMOUS_GOTO_TELNET_PORT },
    { "inside_ftp",	&no_inside_ftp,		CAN_ANONYMOUS_INSIDE_DOMAIN_FTP },
    { "outside_ftp",	&no_outside_ftp,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_FTP },
    { "inside_rlogin",	&no_inside_rlogin,	CAN_ANONYMOUS_INSIDE_DOMAIN_RLOGIN },
    { "outside_rlogin",	&no_outside_rlogin,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_RLOGIN },
    { "suspend",	&no_suspend,		FALSE },
    { "editor",		&no_editor,		FALSE },
    { "shell",		&no_shell,		FALSE },
    { "bookmark",	&no_bookmark,		FALSE },
    { "multibook",	&no_multibook,		FALSE },
    { "bookmark_exec",	&no_bookmark_exec,	FALSE },
    { "option_save",	&no_option_save,	FALSE },
    { "print",		&no_print,		CAN_ANONYMOUS_PRINT },
    { "download",	&no_download,		FALSE },
    { "disk_save",	&no_disk_save,		FALSE },
#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
    { "exec",		&no_exec,		LOCAL_EXECUTION_LINKS_ALWAYS_OFF_FOR_ANONYMOUS },
#endif
    { "lynxcgi",	&no_lynxcgi,		FALSE },
    { "exec_frozen",	&exec_frozen,		FALSE },
    { "goto",		&no_goto,		CAN_ANONYMOUS_GOTO },
    { "jump",		&no_jump,		CAN_ANONYMOUS_JUMP },
    { "file_url",	&no_file_url,		FALSE },
#ifndef DISABLE_NEWS
    { "news_post",	&no_newspost,		FALSE },
    { "inside_news",	&no_inside_news,	CAN_ANONYMOUS_INSIDE_DOMAIN_READ_NEWS },
    { "outside_news",	&no_outside_news,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_READ_NEWS },
#endif
    { "mail",		&no_mail,		CAN_ANONYMOUS_MAIL },
    { "dotfiles",	&no_dotfiles,		FALSE },
    { "useragent",	&no_useragent,		FALSE },
#ifdef SUPPORT_CHDIR
    { "chdir",		&no_chdir,		FALSE },
#endif
#ifdef DIRED_SUPPORT
    { "dired_support",	&no_dired_support,	FALSE },
#ifdef OK_PERMIT
    { "change_exec_perms", &no_change_exec_perms, FALSE },
#endif /* OK_PERMIT */
#endif /* DIRED_SUPPORT */
#ifdef USE_EXTERNALS
    { "externals",	&no_externals,		FALSE },
#endif
    { "lynxcfg_info",	&no_lynxcfg_info,	CAN_ANONYMOUS_VIEW_LYNXCFG_INFO },
#ifndef NO_CONFIG_INFO
    { "lynxcfg_xinfo",	&no_lynxcfg_xinfo,	CAN_ANONYMOUS_VIEW_LYNXCFG_EXTENDED_INFO },
#ifdef HAVE_CONFIG_H
    { "compileopts_info", &no_compileopts_info,	CAN_ANONYMOUS_VIEW_COMPILEOPTS_INFO },
#endif
#endif
    /* put "goto" restrictions on the end, since they are a refinement */
#ifndef DISABLE_BIBP
    { "goto_bibp",	&no_goto_bibp,		CAN_ANONYMOUS_GOTO_BIBP	},
#endif
#ifdef HAVE_CONFIG_H
#ifndef NO_CONFIG_INFO
    { "goto_configinfo", &no_goto_configinfo,	CAN_ANONYMOUS_GOTO_CONFIGINFO },
#endif
#endif
    { "goto_cso",	&no_goto_cso,		CAN_ANONYMOUS_GOTO_CSO },
    { "goto_file",	&no_goto_file,		CAN_ANONYMOUS_GOTO_FILE },
#ifndef DISABLE_FINGER
    { "goto_finger",	&no_goto_finger,	CAN_ANONYMOUS_GOTO_FINGER },
#endif
    { "goto_ftp",	&no_goto_ftp,		CAN_ANONYMOUS_GOTO_FTP },
#ifndef DISABLE_GOPHER
    { "goto_gopher",	&no_goto_gopher,	CAN_ANONYMOUS_GOTO_GOPHER },
#endif
    { "goto_http",	&no_goto_http,		CAN_ANONYMOUS_GOTO_HTTP },
    { "goto_https",	&no_goto_https,		CAN_ANONYMOUS_GOTO_HTTPS },
    { "goto_lynxcgi",	&no_goto_lynxcgi,	CAN_ANONYMOUS_GOTO_LYNXCGI },
    { "goto_lynxexec",	&no_goto_lynxexec,	CAN_ANONYMOUS_GOTO_LYNXEXEC },
    { "goto_lynxprog",	&no_goto_lynxprog,	CAN_ANONYMOUS_GOTO_LYNXPROG },
    { "goto_mailto",	&no_goto_mailto,	CAN_ANONYMOUS_GOTO_MAILTO },
#ifndef DISABLE_NEWS
    { "goto_news",	&no_goto_news,		CAN_ANONYMOUS_GOTO_NEWS },
    { "goto_nntp",	&no_goto_nntp,		CAN_ANONYMOUS_GOTO_NNTP },
#endif
    { "goto_rlogin",	&no_goto_rlogin,	CAN_ANONYMOUS_GOTO_RLOGIN },
#ifndef DISABLE_NEWS
    { "goto_snews",	&no_goto_snews,		CAN_ANONYMOUS_GOTO_SNEWS },
#endif
    { "goto_telnet",	&no_goto_telnet,	CAN_ANONYMOUS_GOTO_TELNET },
    { "goto_tn3270",	&no_goto_tn3270,	CAN_ANONYMOUS_GOTO_TN3270 },
    { "goto_wais",	&no_goto_wais,		CAN_ANONYMOUS_GOTO_WAIS },
};
/* *INDENT-ON* */

/* This will make no difference between '-' and '_'.  It does only in/equality
 * compare.  It assumes that p2 can't contain dashes, but p1 can.  This
 * function is also used (if macro OPTNAME_ALLOW_DASHES doesn't have value of
 * zero) for compare of commandline options -VH
 */
BOOL strn_dash_equ(const char *p1,
		   const char *p2,
		   int len)
{
    while (len--) {
	if (!*p2)
	    return 0;		/* canonical name is shorter */
	switch (*p1) {
	case 0:
	    return 0;
	case '-':
	case '_':
	    if (*p2 != '_')
		return 0;
	    else
		break;
	default:
	    if (*p1 != *p2)
		return 0;
	}
	++p1;
	++p2;
    }
    return 1;
}

/* Uncomment following lines to allow only exact string matching */
/* #define RESTRICT_NM_ALLOW_DASHES 0 */

#ifndef RESTRICT_NM_ALLOW_DASHES
# define RESTRICT_NM_ALLOW_DASHES 1
#endif

#if RESTRICT_NM_ALLOW_DASHES
#	define RESTRICT_NM_EQU(a,b,len) strn_dash_equ(a,b,len)
#else
#	define RESTRICT_NM_EQU(a,b,len) STRNEQ(a,b,len)
#endif

/*
 * Returns the inx'th name from the restrictions table, or null if inx is
 * out of range.
 */
const char *index_to_restriction(unsigned inx)
{
    if (inx < TABLESIZE(restrictions))
	return restrictions[inx].name;
    return NULL;
}

/*
 * Returns the value TRUE/FALSE of a given restriction, or -1 if it is not
 * one that we recognize.
 */
int find_restriction(const char *name,
		     int len)
{
    unsigned i;

    if (len < 0)
	len = (int) strlen(name);
    for (i = 0; i < TABLESIZE(restrictions); i++) {
	if (RESTRICT_NM_EQU(name, restrictions[i].name, len)) {
	    return (*restrictions[i].flag);
	}
    }
    return -1;
}

void parse_restrictions(const char *s)
{
    const char *p;
    const char *word;
    unsigned i;
    BOOLEAN found;

    p = s;
    while (*p) {
	p = LYSkipCBlanks(p);
	if (*p == '\0')
	    break;
	word = p;
	while (*p != ',' && *p != '\0')
	    p++;

	found = FALSE;
	if (RESTRICT_NM_EQU(word, "all", (int) (p - word))) {
	    found = TRUE;
	    for (i = N_SPECIAL_RESTRICT_OPTIONS;
		 i < TABLESIZE(restrictions);
		 i++)
		*(restrictions[i].flag) = TRUE;
	} else if (RESTRICT_NM_EQU(word, "default", (int) (p - word))) {
	    found = TRUE;
	    for (i = N_SPECIAL_RESTRICT_OPTIONS;
		 i < TABLESIZE(restrictions);
		 i++)
		*(restrictions[i].flag) = (BOOLEAN) !restrictions[i].can;
	} else {
	    for (i = 0; i < TABLESIZE(restrictions); i++) {
		if (RESTRICT_NM_EQU(word, restrictions[i].name, (int) (p - word))) {
		    *(restrictions[i].flag) = TRUE;
		    found = TRUE;
		    break;
		}
	    }
	}
	if (!found) {
	    printf("%s: %.*s\n", gettext("unknown restriction"),
		   (int) (p - word), word);
	    exit_immediately(EXIT_FAILURE);
	}
	if (*p)
	    p++;
    }

    /*
     * If shell is restricted, set restrictions on related topics.
     */
    if (no_shell) {
	no_goto_lynxexec = TRUE;
	no_goto_lynxprog = TRUE;
	no_goto_lynxcgi = TRUE;
#ifdef EXEC_LINKS
	local_exec_on_local_files = TRUE;
#endif
    }
}

void print_restrictions_to_fd(FILE *fp)
{
    unsigned i, count = 0;

    for (i = 0; i < TABLESIZE(restrictions); i++) {
	if (*(restrictions[i].flag) == TRUE) {
	    count++;
	}
    }
    if (!count) {
	fprintf(fp, gettext("No restrictions set.\n"));
	return;
    }
    fprintf(fp, gettext("Restrictions set:\n"));
    for (i = 0; i < TABLESIZE(restrictions); i++) {
	if (*(restrictions[i].flag) == TRUE) {
	    /* if "goto" is restricted, don't bother tell about its
	     * refinements
	     */
	    if (StrNCmp(restrictions[i].name, "goto_", 5)
		|| !no_goto)
		fprintf(fp, "   %s\n", restrictions[i].name);
	}
    }
}

#ifdef VMS
#include <jpidef.h>
#include <maildef.h>
#include <starlet.h>

typedef struct _VMSMailItemList {
    short buffer_length;
    short item_code;
    void *buffer_address;
    long *return_length_address;
} VMSMailItemList;

void LYCheckMail(void)
{
    static BOOL firsttime = TRUE, failure = FALSE;
    static char user[13], dir[252];
    static long userlen = 0, dirlen;
    static time_t lastcheck = 0;
    time_t now;
    static short new, lastcount;
    long ucontext = 0, status;
    short flags = MAIL$M_NEWMSG;
    /* *INDENT-OFF* */
    VMSMailItemList
      null_list[] = {{0,0,0,0}},
      jpi_list[]  = {{sizeof(user) - 1,JPI$_USERNAME,(void *)user,&userlen},
		     {0,0,0,0}},
      uilist[]	  = {{0,MAIL$_USER_USERNAME,0,0},
		     {0,0,0,0}},
      uolist[]	  = {{sizeof(new),MAIL$_USER_NEW_MESSAGES,&new,0},
		     {sizeof(dir),MAIL$_USER_FULL_DIRECTORY,dir,&dirlen},
		     {0,0,0,0}};
    /* *INDENT-ON* */

    extern long mail$user_begin();
    extern long mail$user_get_info();
    extern long mail$user_end();

    if (failure)
	return;

    if (firsttime) {
	firsttime = FALSE;
	/* Get the username. */
	status = sys$getjpiw(0, 0, 0, jpi_list, 0, 0, 0);
	if (!(status & 1)) {
	    failure = TRUE;
	    return;
	}
	user[userlen] = '\0';
	LYTrimTrailing(user);
    }

    /* Minimum report interval is 60 sec. */
    time(&now);
    if (now - lastcheck < 60)
	return;
    lastcheck = now;

    /* Get the current newmail count. */
    status = mail$user_begin(&ucontext, null_list, null_list);
    if (!(status & 1)) {
	failure = TRUE;
	return;
    }
    uilist[0].buffer_length = strlen(user);
    uilist[0].buffer_address = user;
    status = mail$user_get_info(&ucontext, uilist, uolist);
    if (!(status & 1)) {
	failure = TRUE;
	return;
    }

    /* Should we report anything to the user? */
    if (new > 0) {
	if (lastcount == 0)
	    /* Have newmail at startup of Lynx. */
	    HTUserMsg(HAVE_UNREAD_MAIL_MSG);
	else if (new > lastcount)
	    /* Have additional mail since last report. */
	    HTUserMsg(HAVE_NEW_MAIL_MSG);
	lastcount = new;
	return;
    }
    lastcount = new;

    /* Clear the context */
    mail$user_end((long *) &ucontext, null_list, null_list);
    return;
}
#else
void LYCheckMail(void)
{
    static BOOL firsttime = TRUE;
    static char *mf;
    static time_t lastcheck;
    static time_t lasttime;
    static long lastsize;
    time_t now;
    struct stat st;

    if (firsttime) {
	mf = LYGetEnv("MAIL");
	firsttime = FALSE;
	time(&lasttime);
    }

    if (mf == NULL)
	return;

    time(&now);
    if (now - lastcheck < 60)
	return;
    lastcheck = now;

    if ((stat(mf, &st) < 0)
	|| !S_ISREG(st.st_mode)) {
	mf = NULL;
	return;
    }

    if (st.st_size > 0) {
	if (((lasttime != st.st_mtime) && (st.st_mtime > st.st_atime))
	    || ((lastsize != 0) && (st.st_size > lastsize)))
	    HTUserMsg(HAVE_NEW_MAIL_MSG);
	else if (lastsize == 0)
	    HTUserMsg(HAVE_MAIL_MSG);
    }
    lastsize = (long) st.st_size;
    lasttime = st.st_mtime;
    return;
}
#endif /* VMS */

/*
 *  This function ensures that an href will be
 *  converted to a fully resolved, absolute URL,
 *  with guessing of the host or expansions of
 *  lead tildes via LYConvertToURL() if needed,
 *  and tweaking/simplifying via HTParse().  It
 *  is used for LynxHome, startfile, homepage,
 *  and 'g'oto entries, after they have been
 *  passed to LYFillLocalFileURL(). - FM
 *  Such URLs have no `base' reference to which they
 *  could be resolved.  LYLegitimizeHREF could not be used.
 */
void LYEnsureAbsoluteURL(char **href,
			 const char *name,
			 int fixit)
{
    char *temp = NULL;

    if (isEmpty(*href))
	return;

    /*
     * Check whether to fill in localhost.  - FM
     */
    LYFillLocalFileURL(href, "file://localhost");

    /*
     * If it is not a URL then make it one.
     */
    if (!strcasecomp(*href, STR_NEWS_URL)) {
	StrAllocCat(*href, "*");
    } else if (!strcasecomp(*href, STR_SNEWS_URL)) {
	StrAllocCat(*href, "/*");
    }

    if (!is_url(*href)) {
	CTRACE((tfp, "%s%s'%s' is not a URL\n",
		NonNull(name), (name ? " " : ""), *href));
	LYConvertToURL(href, fixit);
    }

    temp = HTParse(*href, "", PARSE_ALL);
    if (non_empty(temp))
	StrAllocCopy(*href, temp);
    FREE(temp);
}

static const char *default_scheme = "http://";

static const char *guess_scheme(const char *url)
{
    const char *scheme = NULL;

    if (LYGuessScheme && non_empty(url)) {
	if (0 == strncasecomp(url, "www.", 4)) {
	    scheme = "http://";
	} else if (0 == strncasecomp(url, "ftp.", 4)) {
	    scheme = "ftp://";
	} else if (0 == strncasecomp(url, "gopher.", 7)) {
	    scheme = "gopher://";
	} else if (0 == strncasecomp(url, "wais.", 5)) {
	    scheme = "wais://";
	} else if (0 == strncasecomp(url, "cso.", 4) ||
		   0 == strncasecomp(url, "ns.", 3) ||
		   0 == strncasecomp(url, "ph.", 3)) {
	    scheme = "cso://";
	} else if (0 == strncasecomp(url, "finger.", 7)) {
	    scheme = "finger://";
	} else if (0 == strncasecomp(url, "news.", 5)) {
	    scheme = "news://";
	} else if (0 == strncasecomp(url, "nntp.", 5)) {
	    scheme = "nntp://";
	}
    }
    CTRACE((tfp, "guess_scheme(%s) -> '%s'\n", NonNull(url), NonNull(scheme)));
    return scheme;
}

/*
 * This function rewrites and reallocates a previously allocated string that
 * begins with an Internet host name so that the string begins with its guess
 * of the scheme based on the first field of the host name, or the default
 * scheme if no guess was made.
 */
static void LYAddSchemeForURL(char **AllocatedString)
{
    if (non_empty(*AllocatedString)) {
	char *Str = NULL;
	const char *scheme = guess_scheme(*AllocatedString);

	if (scheme == NULL)
	    scheme = default_scheme;

	StrAllocCopy(Str, scheme);
	StrAllocCat(Str, *AllocatedString);
	StrAllocCopy(*AllocatedString, Str);

	FREE(Str);
    }
}

/*
 * This function rewrites and reallocates a previously allocated string so that
 * the first element is a confirmed Internet host, and returns TRUE, otherwise
 * it does not modify the string and returns FALSE.
 *
 * It first tries the element as is, then, if the element does not end with a
 * dot, it adds prefixes from the (comma separated) prefix list argument, and,
 * if the element does not begin with a dot, suffixes from the (comma
 * separated) suffix list arguments (e.g., www.host.com, then www.host,edu,
 * then www.host.net, then www.host.org).
 *
 * The remaining path, if one is present, will be appended to the expanded
 * host.
 *
 * It also takes into account whether a colon is in the element or suffix, and
 * includes that and what follows as a port field for the expanded host field
 * (e.g, wfbr:8002/dir/lynx should yield www.wfbr.edu:8002/dir/lynx).
 */
static BOOLEAN LYExpandHostForURL(char **AllocatedString,
				  char *prefix_list,
				  char *suffix_list)
{
    char *DomainPrefix = NULL;
    const char *StartP, *EndP;
    char *DomainSuffix = NULL;
    const char *StartS, *EndS;
    char *Str = NULL, *StrColon = NULL, *MsgStr = NULL;
    char *Host = NULL, *HostColon = NULL, *host = NULL;
    char *Path = NULL;
    char *Fragment = NULL;
    BOOLEAN GotHost = FALSE;
    BOOLEAN Startup = (BOOL) (helpfilepath == NULL);

    /*
     * If it's a NULL or zero-length string, or if it begins with a slash or
     * hash, don't continue pointlessly.  - FM
     */
    if (isEmpty(*AllocatedString) ||
	*AllocatedString[0] == '/' ||
	*AllocatedString[0] == '#') {
	return GotHost;
    }

    /*
     * If it's a partial or relative path, don't continue pointlessly.  - FM
     */
    if (!StrNCmp(*AllocatedString, "..", 2) ||
	!StrNCmp(*AllocatedString, "./", 2)) {
	return GotHost;
    }

    /*
     * Make a clean copy of the string, and trim off the path if one is
     * present, but save the information so we can restore the path after
     * filling in the Host[:port] field.  - FM
     */
    StrAllocCopy(Str, *AllocatedString);
    if ((Path = StrChr(Str, '/')) != NULL) {
	/*
	 * Have a path.  Any fragment should already be included in Path.  - FM
	 */
	*Path = '\0';
    } else {
	/*
	 * No path, so check for a fragment and trim that, to be restored after
	 * filling in the Host[:port] field.  - FM
	 */
	Fragment = trimPoundSelector(Str);
    }

    /*
     * If the potential host string has a colon, assume it begins a port field,
     * and trim it off, but save the information so we can restore the port
     * field after filling in the host field.  - FM
     */
    if ((StrColon = strrchr(Str, ':')) != NULL &&
	isdigit(UCH(StrColon[1])) && StrChr(StrColon, ']') == NULL) {
	if (StrColon == Str) {
	    goto cleanup;
	}
	*StrColon = '\0';
    }

    /*
     * Do a DNS test on the potential host field as presently trimmed.  - FM
     */
    StrAllocCopy(host, Str);
    strip_userid(host, FALSE);
    HTUnEscape(host);
    if (LYCursesON) {
	StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
	StrAllocCat(MsgStr, host);
	StrAllocCat(MsgStr, FIRST_SEGMENT);
	HTProgress(MsgStr);
    } else if (Startup && !dump_output_immediately) {
	fprintf(stdout, "%s '%s'%s\r\n", WWW_FIND_MESSAGE, host, FIRST_SEGMENT);
    }
#ifdef INET6
    if (HTCheckAddrInfo(host, 80))
#else
    if (LYCheckHostByName(host))
#endif /* INET6 */
    {
	/*
	 * Clear any residual interrupt.  - FM
	 */
	if (LYCursesON && HTCheckForInterrupt()) {
	    CTRACE((tfp,
		    "LYExpandHostForURL: Ignoring interrupt because '%s' resolved.\n",
		    host));
	}

	/*
	 * Return success.  - FM
	 */
	GotHost = TRUE;
	goto cleanup;
    } else if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
	/*
	 * Give the user chance to interrupt lookup cycles.  - KW & FM
	 */
	CTRACE((tfp,
		"LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
		host));

	/*
	 * Return failure.  - FM
	 */
	goto cleanup;
    }

    /*
     * Set the first prefix, making it a zero-length string if the list is NULL
     * or if the potential host field ends with a dot.  - FM
     */
    StartP = ((prefix_list && Str[strlen(Str) - 1] != '.')
	      ? prefix_list
	      : "");
    /*
     * If we have a prefix, but the allocated string is one of the common host
     * prefixes, make our prefix a zero-length string.  - FM
     */
    if (*StartP && *StartP != '.') {
	if (guess_scheme(*AllocatedString) != NULL) {
	    StartP = "";
	}
    }
    while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
	StartP++;		/* Skip whitespace and separators */
    }
    EndP = StartP;
    while (*EndP && !WHITE(*EndP) && *EndP != ',') {
	EndP++;			/* Find separator */
    }
    StrAllocCopy(DomainPrefix, StartP);
    DomainPrefix[EndP - StartP] = '\0';

    /*
     * Test each prefix with each suffix.  - FM
     */
    do {
	/*
	 * Set the first suffix, making it a zero-length string if the list is
	 * NULL or if the potential host field begins with a dot.  - FM
	 */
	StartS = ((suffix_list && *Str != '.')
		  ? suffix_list
		  : "");
	while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
	    StartS++;		/* Skip whitespace and separators */
	}
	EndS = StartS;
	while (*EndS && !WHITE(*EndS) && *EndS != ',') {
	    EndS++;		/* Find separator */
	}
	StrAllocCopy(DomainSuffix, StartS);
	DomainSuffix[EndS - StartS] = '\0';

	/*
	 * Create domain names and do DNS tests.  - FM
	 */
	do {
	    StrAllocCopy(Host, DomainPrefix);
	    StrAllocCat(Host, ((*Str == '.') ? (Str + 1) : Str));
	    if (Host[strlen(Host) - 1] == '.') {
		Host[strlen(Host) - 1] = '\0';
	    }
	    StrAllocCat(Host, DomainSuffix);
	    if ((HostColon = strrchr(Host, ':')) != NULL &&
		isdigit(UCH(HostColon[1]))) {
		*HostColon = '\0';
	    }
	    StrAllocCopy(host, Host);
	    HTUnEscape(host);
	    if (LYCursesON) {
		StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
		StrAllocCat(MsgStr, host);
		StrAllocCat(MsgStr, GUESSING_SEGMENT);
		HTProgress(MsgStr);
	    } else if (Startup && !dump_output_immediately) {
		fprintf(stdout, "%s '%s'%s\n", WWW_FIND_MESSAGE, host, GUESSING_SEGMENT);
	    }
	    GotHost = LYCheckHostByName(host);
	    if (HostColon != NULL) {
		*HostColon = ':';
	    }
	    if (GotHost == FALSE) {
		/*
		 * Give the user chance to interrupt lookup cycles.  - KW
		 */
		if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
		    CTRACE((tfp,
			    "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
			    host));
		    goto cleanup;	/* We didn't find a valid name. */
		}

		/*
		 * Advance to the next suffix, or end of suffix list.  - FM
		 */
		StartS = ((*EndS == '\0') ? EndS : (EndS + 1));
		while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
		    StartS++;	/* Skip whitespace and separators */
		}
		EndS = StartS;
		while (*EndS && !WHITE(*EndS) && *EndS != ',') {
		    EndS++;	/* Find separator */
		}
		LYStrNCpy(DomainSuffix, StartS, (EndS - StartS));
	    }
	} while ((GotHost == FALSE) && (*DomainSuffix != '\0'));

	if (GotHost == FALSE) {
	    /*
	     * Advance to the next prefix, or end of prefix list.  - FM
	     */
	    StartP = ((*EndP == '\0') ? EndP : (EndP + 1));
	    while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
		StartP++;	/* Skip whitespace and separators */
	    }
	    EndP = StartP;
	    while (*EndP && !WHITE(*EndP) && *EndP != ',') {
		EndP++;		/* Find separator */
	    }
	    LYStrNCpy(DomainPrefix, StartP, (EndP - StartP));
	}
    } while ((GotHost == FALSE) && (*DomainPrefix != '\0'));

    /*
     * If a test passed, restore the port field if we had one and there is no
     * colon in the expanded host, and the path if we had one, and reallocate
     * the original string with the expanded Host[:port] field included.  - FM
     */
    if (GotHost) {
	if (StrColon && StrChr(Host, ':') == NULL) {
	    *StrColon = ':';
	    StrAllocCat(Host, StrColon);
	}
	if (Path) {
	    *Path = '/';
	    StrAllocCat(Host, Path);
	} else if (Fragment) {
	    StrAllocCat(Host, "/");
	    restorePoundSelector(Fragment);
	    StrAllocCat(Host, Fragment);
	}
	StrAllocCopy(*AllocatedString, Host);
    }

    /*
     * Clear any residual interrupt.  - FM
     */
    if (LYCursesON && HTCheckForInterrupt()) {
	CTRACE((tfp,
		"LYExpandHostForURL: Ignoring interrupt because '%s' %s.\n",
		host,
		(GotHost ? "resolved" : "timed out")));
    }

    /*
     * Clean up and return the last test result.  - FM
     */
  cleanup:
    FREE(DomainPrefix);
    FREE(DomainSuffix);
    FREE(Str);
    FREE(MsgStr);
    FREE(Host);
    FREE(host);
    return GotHost;
}
/*
 * Rewrite and reallocate a previously allocated string as a file URL if the
 * string resolves to a file or directory on the local system, otherwise as an
 * http URL.  - FM
 */
void LYConvertToURL(char **AllocatedString,
		    int fixit)
{
    char *old_string = *AllocatedString;
    char *temp = NULL;
    char *cp = NULL;

#ifndef VMS
    struct stat st;
#endif /* !VMS */

    if (isEmpty(old_string))
	return;

#if defined(USE_DOS_DRIVES)
    {
	char *cp_url = *AllocatedString;

	for (; *cp_url != '\0'; cp_url++)
	    if (*cp_url == '\\')
		*cp_url = '/';
	cp_url--;
	if (LYIsDosDrive(*AllocatedString) && *cp_url == ':')
	    LYAddPathSep(AllocatedString);
    }
#endif /* USE_DOS_DRIVES */

    *AllocatedString = NULL;	/* so StrAllocCopy doesn't free it */
    StrAllocCopy(*AllocatedString, "file://localhost");

    if (*old_string != '/') {
	char *fragment = NULL;

#if defined(USE_DOS_DRIVES)
	StrAllocCat(*AllocatedString, "/");
#endif /* USE_DOS_DRIVES */
#ifdef VMS
	/*
	 * Not a SHELL pathspec.  Get the full VMS spec and convert it.
	 */
	char *cur_dir = NULL;
	static char url_file[LY_MAXPATH], file_name[LY_MAXPATH], dir_name[LY_MAXPATH];
	unsigned long context = 0;

	$DESCRIPTOR(url_file_dsc, url_file);
	$DESCRIPTOR(file_name_dsc, file_name);
	if (LYIsTilde(*old_string)) {
	    /*
	     * On VMS, we'll accept '~' on the command line as Home_Dir(), and
	     * assume the rest of the path, if any, has SHELL syntax.
	     */
	    StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
	    if ((cp = StrChr(old_string, '/')) != NULL) {
		/*
		 * Append rest of path, if present, skipping "user" if "~user"
		 * was entered, simplifying, and eliminating any residual
		 * relative elements.  - FM
		 */
		StrAllocCopy(temp, cp);
		LYTrimRelFromAbsPath(temp);
		StrAllocCat(*AllocatedString, temp);
		FREE(temp);
	    }
	    goto have_VMS_URL;
	} else {
	    fragment = trimPoundSelector(old_string);
	    LYStrNCpy(url_file, old_string, sizeof(url_file) - 1);
	}
	url_file_dsc.dsc$w_length = (short) strlen(url_file);
	if (1 & lib$find_file(&url_file_dsc, &file_name_dsc, &context,
			      0, 0, 0, 0)) {
	    /*
	     * We found the file.  Convert to a URL pathspec.
	     */
	    if ((cp = StrChr(file_name, ';')) != NULL) {
		*cp = '\0';
	    }
	    LYLowerCase(file_name);
	    StrAllocCat(*AllocatedString, HTVMS_wwwName(file_name));
	    if ((cp = StrChr(old_string, ';')) != NULL) {
		StrAllocCat(*AllocatedString, cp);
	    }
	    if (fragment != NULL) {
		restorePoundSelector(fragment);
		StrAllocCat(*AllocatedString, fragment);
		fragment = NULL;
	    }
	} else if ((NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) &&
		   0 == chdir(old_string)) {
	    /*
	     * Probably a directory.  Try converting that.
	     */
	    StrAllocCopy(cur_dir, dir_name);
	    restorePoundSelector(fragment);
	    if (NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) {
		/*
		 * Yup, we got it!
		 */
		LYLowerCase(dir_name);
		StrAllocCat(*AllocatedString, dir_name);
		if (fragment != NULL) {
		    StrAllocCat(*AllocatedString, fragment);
		    fragment = NULL;
		}
	    } else {
		/*
		 * Nope.  Assume it's an http URL with the "http://" defaulted,
		 * if we can't rule out a bad VMS path.
		 */
		fragment = NULL;
		if (StrChr(old_string, '[') ||
		    ((cp = StrChr(old_string, ':')) != NULL &&
		     !isdigit(UCH(cp[1]))) ||
		    !LYExpandHostForURL(&old_string,
					URLDomainPrefixes,
					URLDomainSuffixes)) {
		    /*
		     * Probably a bad VMS path (but can't be sure).  Use
		     * original pathspec for the error message that will
		     * result.
		     */
		    sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
		    CTRACE((tfp,
			    "Can't find '%s'  Will assume it's a bad path.\n",
			    old_string));
		    StrAllocCat(*AllocatedString, url_file);
		} else {
		    LYAddSchemeForURL(&old_string);
		    StrAllocCopy(*AllocatedString, old_string);
		}
	    }
	} else {
	    /*
	     * Nothing found.  Assume it's an http URL with the "http://"
	     * defaulted, if we can't rule out a bad VMS path.
	     */
	    restorePoundSelector(fragment);
	    fragment = NULL;

	    if (StrChr(old_string, '[') ||
		((cp = StrChr(old_string, ':')) != NULL &&
		 !isdigit(UCH(cp[1]))) ||
		!LYExpandHostForURL(&old_string,
				    URLDomainPrefixes,
				    URLDomainSuffixes)) {
		/*
		 * Probably a bad VMS path (but can't be sure).  Use original
		 * pathspec for the error message that will result.
		 */
		sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
		CTRACE((tfp, "Can't find '%s'  Will assume it's a bad path.\n",
			old_string));
		StrAllocCat(*AllocatedString, url_file);
	    } else {
		LYAddSchemeForURL(&old_string);
		StrAllocCopy(*AllocatedString, old_string);
	    }
	}
	lib$find_file_end(&context);
	FREE(cur_dir);
      have_VMS_URL:
	CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
#else /* not VMS: */
#if defined(USE_DOS_DRIVES)
#ifdef _WINDOWS
	if (*old_string == '.') {
	    char fullpath[MAX_PATH + 1];
	    char *filepart = NULL;
	    DWORD chk;

	    chk = GetFullPathNameA(old_string, MAX_PATH + 1,
				   fullpath, &filepart);
	    if (chk != 0) {
		StrAllocCopy(temp, wwwName(fullpath));
		StrAllocCat(*AllocatedString, temp);
		FREE(temp);
		CTRACE((tfp, "Converted '%s' to '%s'\n",
			old_string, *AllocatedString));
	    } else {
		StrAllocCat(*AllocatedString, old_string);
	    }
	}
#else
	if (strlen(old_string) == 1 && *old_string == '.') {
	    /*
	     * They want .
	     */
	    char curdir[LY_MAXPATH];

	    StrAllocCopy(temp, wwwName(Current_Dir(curdir)));
	    StrAllocCat(*AllocatedString, temp);
	    FREE(temp);
	    CTRACE((tfp, "Converted '%s' to '%s'\n",
		    old_string, *AllocatedString));
	}
#endif
	else
#endif /* USE_DOS_DRIVES */
	if (LYIsTilde(*old_string)) {
	    char *his_home = NULL;

	    StrAllocCopy(his_home, old_string);
	    LYTildeExpand(&his_home, FALSE);
	    StrAllocCat(*AllocatedString, his_home);
	    FREE(his_home);

	    CTRACE((tfp, "Converted '%s' to '%s'\n",
		    old_string, *AllocatedString));
	} else {
	    /*
	     * Create a full path to the current default directory.
	     */
	    char curdir[LY_MAXPATH];
	    char *temp2 = NULL;
	    BOOL is_local = FALSE;

	    Current_Dir(curdir);
	    /*
	     * Concatenate and simplify, trimming any residual relative
	     * elements.  - FM
	     */
#if defined (USE_DOS_DRIVES)
	    if (old_string[1] != ':' && old_string[1] != '|') {
		StrAllocCopy(temp, wwwName(curdir));
		LYAddHtmlSep(&temp);
		LYStrNCpy(curdir, temp, (sizeof(curdir) - 1));
		StrAllocCat(temp, old_string);
	    } else {
		curdir[0] = '\0';
		/* 1998/01/13 (Tue) 12:24:33 */
		if (old_string[1] == '|')
		    old_string[1] = ':';
		StrAllocCopy(temp, old_string);

		if (strlen(temp) == 2 && LYIsDosDrive(temp))
		    LYAddPathSep(&temp);
	    }
#else
	    StrAllocCopy(temp, curdir);
	    StrAllocCat(temp, "/");
	    StrAllocCat(temp, old_string);
#endif /* USE_DOS_DRIVES */
	    LYTrimRelFromAbsPath(temp);
	    CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
	    if ((stat(temp, &st) > -1) ||
		LYCanReadFile(temp)) {
		/*
		 * It is a subdirectory or file on the local system.
		 */
#if defined (USE_DOS_DRIVES)
		/* Don't want to see DOS local paths like c: escaped  */
		/* especially when we really have file://localhost/   */
		/* at the beginning.  To avoid any confusion we allow */
		/* escaping the path if URL specials % or # present.  */
		if (StrChr(temp, '#') == NULL && StrChr(temp, '%') == NULL)
		    StrAllocCopy(cp, temp);
		else
		    cp = HTEscape(temp, URL_PATH);
#else
		cp = HTEscape(temp, URL_PATH);
#endif /* USE_DOS_DRIVES */
		StrAllocCat(*AllocatedString, cp);
		FREE(cp);
		CTRACE((tfp, "Converted '%s' to '%s'\n",
			old_string, *AllocatedString));
		is_local = TRUE;
	    } else {
		char *cp2 = NULL;

		StrAllocCopy(temp2, curdir);
		LYAddPathSep(&temp2);
		StrAllocCopy(cp, old_string);
		fragment = trimPoundSelector(cp);
		HTUnEscape(cp);	/* unescape given path without fragment */
		StrAllocCat(temp2, cp);		/* append to current dir  */
		StrAllocCopy(cp2, temp2);	/* keep a copy in cp2     */
		LYTrimRelFromAbsPath(temp2);
#ifdef WIN_EX			/* 1998/07/31 (Fri) 09:09:03 */
		HTUnEscape(temp2);	/* for LFN */
#endif

		if (strcmp(temp2, temp) != 0 &&
		    ((stat(temp2, &st) > -1) ||
		     LYCanReadFile(temp2))) {
		    /*
		     * It is a subdirectory or file on the local system with
		     * escaped characters and/or a fragment to be appended to
		     * the URL.  - FM
		     */

		    FREE(temp);
		    if (strcmp(cp2, temp2) == 0) {
			/*
			 * LYTrimRelFromAbsPath did nothing, use old_string as
			 * given.  - kw
			 */
			temp = HTEscape(curdir, URL_PATH);
			LYAddHtmlSep(&temp);
			StrAllocCat(temp, old_string);
		    } else {
			temp = HTEscape(temp2, URL_PATH);
			if (fragment != NULL) {
			    restorePoundSelector(fragment);
			    StrAllocCat(temp, fragment);
			}
		    }
		    StrAllocCat(*AllocatedString, temp);
		    CTRACE((tfp, "Converted '%s' to '%s'\n",
			    old_string, *AllocatedString));
		    is_local = TRUE;

		} else if (StrChr(curdir, '#') != NULL ||
			   StrChr(curdir, '%') != NULL) {
		    /*
		     * If PWD has some unusual characters, construct a filename
		     * in temp where those are escaped.  This is mostly to
		     * prevent this function from returning with some weird URL
		     * if the LYExpandHostForURL tests further down fail.  - kw
		     */
		    FREE(temp);
		    if (strcmp(cp2, temp2) == 0) {
			/*
			 * LYTrimRelFromAbsPath did nothing, use old_string as
			 * given.  - kw
			 */
			temp = HTEscape(curdir, URL_PATH);
			LYAddHtmlSep(&temp);
			StrAllocCat(temp, old_string);
		    } else {
			temp = HTEscape(temp2, URL_PATH);
			if (fragment != NULL) {
			    restorePoundSelector(fragment);
			    StrAllocCat(temp, fragment);
			}
		    }
		}
		FREE(cp);
		FREE(cp2);
	    }
	    if (is_local == FALSE) {
		/*
		 * It's not an accessible subdirectory or file on the local
		 * system, so assume it's a URL request and guess the scheme
		 * with "http://" as the default.
		 */
		CTRACE((tfp, "Can't stat() or fopen() '%s'\n",
			temp2 ? temp2 : temp));
#ifdef WIN_EX			/* 1998/01/13 (Tue) 09:07:37 */
		{
		    const char *p, *q;
		    char buff[LY_MAXPATH + 128];

		    p = Home_Dir();
		    q = temp2 ? temp2 : temp;

		    if (strlen(q) == 3 && LYIsDosDrive(q)) {
			sprintf(buff,
				"'%s' not exist, Goto LynxHome '%s'.", q, p);
			_statusline(buff);
			LYSleepAlert();
			FREE(temp);
			StrAllocCat(*AllocatedString, p);
			goto Retry;
		    }
		}
#endif
		if (LYExpandHostForURL(&old_string,
				       URLDomainPrefixes,
				       URLDomainSuffixes)) {
		    LYAddSchemeForURL(&old_string);
		    StrAllocCopy(*AllocatedString, old_string);
		} else if (fixit) {
		    StrAllocCopy(*AllocatedString, old_string);
		} else {
		    /* Return file URL for the file that does not exist */
		    StrAllocCat(*AllocatedString, temp);
		}
#ifdef WIN_EX
	      Retry:
#endif
		CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
	    }
	    FREE(temp);
	    FREE(temp2);
	}
#endif /* VMS */
    } else {
	/*
	 * Path begins with a slash.  Simplify and use it.
	 */
	if (old_string[1] == '\0') {
	    /*
	     * Request for root.  Respect it on Unix, but on VMS we treat that
	     * as a listing of the login directory.  - FM
	     */
#ifdef VMS
	    StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
#else
	    StrAllocCat(*AllocatedString, "/");
	} else if ((stat(old_string, &st) > -1) ||
		   LYCanReadFile(old_string)) {
	    /*
	     * It is an absolute directory or file on the local system.  - KW
	     */
	    StrAllocCopy(temp, old_string);
	    LYTrimRelFromAbsPath(temp);
	    CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
	    cp = HTEscape(temp, URL_PATH);
	    StrAllocCat(*AllocatedString, cp);
	    FREE(cp);
	    FREE(temp);
	    CTRACE((tfp, "Converted '%s' to '%s'\n",
		    old_string, *AllocatedString));
#endif /* VMS */
	} else if (LYIsTilde(old_string[1])) {
	    /*
	     * Has a Home_Dir() reference.  Handle it as if there weren't a
	     * lead slash.  - FM
	     */
	    StrAllocCat(*AllocatedString, wwwName(Home_Dir()));
	    if ((cp = StrChr((old_string + 1), '/')) != NULL) {
		/*
		 * Append rest of path, if present, skipping "user" if "~user"
		 * was entered, simplifying, and eliminating any residual
		 * relative elements.  - FM
		 */
		StrAllocCopy(temp, cp);
		LYTrimRelFromAbsPath(temp);
		StrAllocCat(*AllocatedString, temp);
		FREE(temp);
	    }
	} else {
	    /*
	     * Normal absolute path.  Simplify, trim any residual relative
	     * elements, and append it.  - FM
	     */
	    StrAllocCopy(temp, old_string);
	    LYTrimRelFromAbsPath(temp);
	    StrAllocCat(*AllocatedString, temp);
	    FREE(temp);
	}
	CTRACE((tfp, "Converted '%s' to '%s'\n",
		old_string, *AllocatedString));
    }
    FREE(old_string);
    /* Pause so we can read the messages before invoking curses */
    CTRACE_SLEEP(AlertSecs);
}

#if defined(_WINDOWS)		/* 1998/06/23 (Tue) 16:45:20 */

int win32_check_interrupt(void)
{
    int c;

    if (kbhit()) {
	c = LYgetch();
	/** Keyboard 'Z' or 'z', or Control-G or Control-C **/
	if (LYCharIsINTERRUPT(c) || c == 0x1b) {
	    return TRUE;
	}
    }
    return FALSE;
}

#if (!defined(__MINGW32__) && !defined(sleep)) || (defined(__MINGW32__) && !defined(HAVE_SLEEP))
void sleep(unsigned sec)
{
    unsigned int i, j;

    for (j = 0; j < sec; j++) {
	for (i = 0; i < 10; i++) {
	    Sleep(100);
	    if (kbhit()) {
		(void) LYgetch();
		return;
	    }
	}
    }
}
#endif /* !__MINGW32__ */
#endif /* _WINDOWS */
/*
 * This function expects an absolute Unix or VMS SHELL path spec as an
 * allocated string, simplifies it, and trims out any residual relative
 * elements.  It also checks whether the path had a terminal slash, and if it
 * didn't, makes sure that the simplified path doesn't either.  If it's a
 * directory, our convention is to exclude "Up to parent" links when a terminal
 * slash is present.  - FM
 */
void LYTrimRelFromAbsPath(char *path)
{
    char *cp;
    int i;
    BOOL TerminalSlash;

    /*
     * Make sure we have a pointer to an absolute path.  - FM
     */
    if (path == NULL || !LYIsPathSep(*path))
	return;

    /*
     * Check whether the path has a terminal slash.  - FM
     */
    TerminalSlash = (BOOL) (LYIsPathSep(path[(strlen(path) - 1)]));

    /*
     * Simplify the path and then do any necessary trimming.  - FM
     */
    HTSimplify(path, TRUE);
    cp = path;
    while (cp[1] == '.') {
	if (cp[2] == '\0') {
	    /*
	     * Eliminate trailing dot.  - FM
	     */
	    cp[1] = '\0';
	} else if (LYIsPathSep(cp[2])) {
	    /*
	     * Skip over the "/." of a "/./".  - FM
	     */
	    cp += 2;
	} else if (cp[2] == '.' && cp[3] == '\0') {
	    /*
	     * Eliminate trailing dotdot.  - FM
	     */
	    cp[1] = '\0';
	} else if (cp[2] == '.' && cp[3] == '/') {
	    /*
	     * Skip over the "/.." of a "/../".  - FM
	     */
	    cp += 3;
	} else {
	    /*
	     * Done trimming.  - FM
	     */
	    break;
	}
    }

    /*
     * Load any shifts into path, and eliminate any terminal slash created by
     * HTSimplify() or our walk, but not present originally.  - FM
     */
    if (cp > path) {
	for (i = 0; cp[i] != '\0'; i++)
	    path[i] = cp[i];
	path[i] = '\0';
    }
    if (TerminalSlash == FALSE) {
	LYTrimPathSep(path);
    }
}

/*
 * Example Client-Side Include interface.
 *
 * This is called from SGML.c and simply returns markup for reporting the URL
 * of the document being loaded if a comment begins with "<!--#lynxCSI".  The
 * markup will be included as if it were in the document.  Move this function
 * to a separate module for doing this kind of thing seriously, someday.  - FM
 */
void LYDoCSI(char *url,
	     const char *comment,
	     char **csi)
{
    const char *cp = comment;

    if (cp == NULL)
	return;

    if (StrNCmp(cp, "!--#", 4))
	return;

    cp += 4;
    if (!strncasecomp(cp, "lynxCSI", 7)) {
	StrAllocCat(*csi, "\n<p align=\"center\">URL: ");
	StrAllocCat(*csi, url);
	StrAllocCat(*csi, "</p>\n\n");
    }

    return;
}

#ifdef VMS
/*
 * Define_VMSLogical -- Fote Macrides 04-Apr-1995
 * Define VMS logicals in the process table.
 */
void Define_VMSLogical(char *LogicalName,
		       char *LogicalValue)
{
    $DESCRIPTOR(lname, "");
    $DESCRIPTOR(lvalue, "");
    $DESCRIPTOR(ltable, "LNM$PROCESS");

    if (isEmpty(LogicalName))
	return;

    lname.dsc$w_length = strlen(LogicalName);
    lname.dsc$a_pointer = LogicalName;

    if (isEmpty(LogicalValue)) {
	lib$delete_logical(&lname, &ltable);
	return;
    }

    lvalue.dsc$w_length = strlen(LogicalValue);
    lvalue.dsc$a_pointer = LogicalValue;
    lib$set_logical(&lname, &lvalue, &ltable, 0, 0);
    return;
}
#endif /* VMS */

#ifdef LY_FIND_LEAKS
static void LYHomeDir_free(void)
{
    FREE(HomeDir);
}
#endif /* LY_FIND_LEAKS */

char *Current_Dir(char *pathname)
{
    char *result;

#ifdef HAVE_GETCWD
    result = getcwd(pathname, (size_t) LY_MAXPATH);
#else
    result = getwd(pathname);
#endif /* NO_GETCWD */
    if (result == 0)
	strcpy(pathname, ".");
    return pathname;
}

/*
 * Verify that the given path refers to an existing directory, returning the
 * string if the directory exists.  If not, return null.
 */
static char *CheckDir(char *path)
{
    struct stat stat_info;

    if (!LYisAbsPath(path)
	|| (HTStat(path, &stat_info) < 0
	    || !S_ISDIR(stat_info.st_mode))) {
	path = NULL;
    }
    CTRACE((tfp, "CheckDir(%s) %s\n", NonNull(path), path ? "OK" : "ERR"));
    return path;
}

/*
 * Lookup various possibilities for $HOME, and check that the directory exists.
 */
static char *HomeEnv(void)
{
    char *result = CheckDir(LYGetEnv("HOME"));

#if defined (USE_DOS_DRIVES) && defined(_WIN32)
    if (result == 0) {
	char *head;
	char *leaf;
	static char *temp = NULL;

	result = w32_get_shell_folder("Personal");
	if (result == 0) {
	    /* Windows Vista/7 */
	    if ((head = LYGetEnv("USERPROFILE")) != 0) {
		HTSprintf0(&temp, "%s%sDocuments", head, FILE_SEPARATOR);
		result = CheckDir(temp);
		if (result == 0) {
		    /* Windows 2000 */
		    HTSprintf0(&temp, "%s%sMy Documents", head, FILE_SEPARATOR);
		    result = CheckDir(temp);
		}
	    }
	}
	/* NT4 */
	if (result == 0) {
	    if ((head = LYGetEnv("HOMEDRIVE")) != 0) {
		if ((leaf = LYGetEnv("HOMEPATH")) != 0) {
		    HTSprintf0(&temp, "%s%s%s", head, FILE_SEPARATOR, leaf);
		    result = CheckDir(temp);
		}
	    }
	}
	/* General M$ */
#ifdef USE_PROGRAM_DIR
	if (result == 0)
	    result = CheckDir(program_dir);
#endif
	if (result == 0)
	    result = CheckDir(LYGetEnv("TEMP"));
	if (result == 0)
	    result = CheckDir(LYGetEnv("TMP"));
	if (result == 0) {
	    if ((head = LYGetEnv("SystemDrive")) != 0) {
		HTSprintf0(&temp, "%s%s", head, FILE_SEPARATOR);
		result = CheckDir(temp);
	    }
	}
	if (result == 0)
	    result = CheckDir("C:" FILE_SEPARATOR);
    }
#endif

    return result;
}

const char *Home_Dir(void)
{
    static const char *homedir = NULL;
    char *cp = NULL;

    if (homedir == NULL) {
	if ((cp = HomeEnv()) == NULL) {
#ifdef VMS
	    if ((cp = LYGetEnv("SYS$LOGIN")) == NULL
		&& (cp = LYGetEnv("SYS$SCRATCH")) == NULL) {
		cp = "sys$scratch:";
	    }
	    StrAllocCopy(HomeDir, cp);
#else
#ifdef UNIX
#ifdef HAVE_UTMP
	    /*
	     * One could use getlogin() and getpwnam() here instead.
	     */
	    struct passwd *pw = getpwuid(geteuid());

	    if (pw && pw->pw_dir) {
		StrAllocCopy(HomeDir, pw->pw_dir);
	    } else
#endif
	    {
		/*
		 * Use /tmp; it should be writable.
		 */
		StrAllocCopy(HomeDir, "/tmp");
	    }
#endif
#endif /* VMS */
	} else {
	    StrAllocCopy(HomeDir, cp);
	}
	homedir = (const char *) HomeDir;
#ifdef LY_FIND_LEAKS
	atexit(LYHomeDir_free);
#endif
    }
    if (homedir == NULL) {
	printf("%s\n", gettext("Cannot find HOME directory"));
	exit_immediately(EXIT_FAILURE);
    }
    return homedir;
}

/*
 * Return a pointer to the final leaf of the given pathname, If no pathname
 * separators are found, returns the original pathname.  The leaf may be
 * empty.
 */
char *LYPathLeaf(char *pathname)
{
    char *leaf;

#ifdef UNIX
    if ((leaf = strrchr(pathname, '/')) != 0) {
	leaf++;
    }
#else
#ifdef VMS
    if ((leaf = strrchr(pathname, ']')) == 0)
	leaf = strrchr(pathname, ':');
    if (leaf != 0)
	leaf++;
#else
    int n;

    for (leaf = 0, n = (int) strlen(pathname) - 1; n >= 0; n--) {
	if (StrChr("\\/:", pathname[n]) != 0) {
	    leaf = pathname + n + 1;
	    break;
	}
    }
#endif
#endif
    return (leaf != 0) ? leaf : pathname;
}

/*
 * This function checks the acceptability of file paths that are intended to be
 * off the home directory.  The file path should be passed in fbuffer, together
 * with the size of the buffer.  The function simplifies the file path, and if
 * it is acceptable, loads it into fbuffer and returns TRUE.  Otherwise, it
 * does not modify fbuffer and returns FALSE.  If a subdirectory is present and
 * the path does not begin with "./", that is prefixed to make the situation
 * clear.  - FM
 */
BOOLEAN LYPathOffHomeOK(char *fbuffer,
			size_t fbuffer_size)
{
    char *file = NULL;
    char *cp, *cp1;

    /*
     * Make sure we have an fbuffer and a string in it.  - FM
     */
    if (fbuffer_size < 2 || isEmpty(fbuffer)) {
	return (FALSE);
    }
    StrAllocCopy(file, fbuffer);
    cp = file;

    /*
     * Check for an inappropriate reference to the home directory, and correct
     * it if we can.  - FM
     */
#ifdef VMS
    if (!strncasecomp(cp, "sys$login", 9)) {
	if (*(cp + 9) == '\0') {
	    /*
	     * Reject "sys$login".  - FM
	     */
	    FREE(file);
	    return (FALSE);
	}
	if (*(cp + 9) == ':') {
	    cp += 10;
	    if (*cp == '\0') {
		/*
		 * Reject "sys$login:".  Otherwise, we have converted
		 * "sys$login:file" to "file", or have left a strange path for
		 * VMS as it was originally.  - FM
		 */
		FREE(file);
		return (FALSE);
	    }
	}
    }
#endif /* VMS */
    if (LYIsTilde(cp[0])) {
	if (LYIsPathSep(cp[1])) {
	    if (cp[2] != '\0') {
		if (StrChr((cp + 2), '/') != NULL) {
		    /*
		     * Convert "~/subdir(s)/file" to "./subdir(s)/file".  - FM
		     */
		    *cp = '.';
		} else {
		    /*
		     * Convert "~/file" to "file".  - FM
		     */
		    cp += 2;
		}
	    } else {
		/*
		 * Reject "~/".  - FM
		 */
		FREE(file);
		return (FALSE);
	    }
	} else if ((*(cp + 1) != '\0') &&
		   (cp1 = StrChr((cp + 1), '/')) != NULL) {
	    cp = (cp1 - 1);
	    if (*(cp + 2) != '\0') {
		if (StrChr((cp + 2), '/') != NULL) {
		    /*
		     * Convert "~user/subdir(s)/file" to "./subdir(s)/file". 
		     * If user is someone else, we covered a spoof.  Otherwise,
		     * we simplified.  - FM
		     */
		    *cp = '.';
		} else {
		    /*
		     * Convert "~user/file" to "file".  - FM
		     */
		    cp += 2;
		}
	    } else {
		/*
		 * Reject "~user/".  - FM
		 */
		FREE(file);
		return (FALSE);
	    }
	} else {
	    /*
	     * Reject "~user".  - FM
	     */
	    FREE(file);
	    return (FALSE);
	}
    }
#ifdef VMS
    /*
     * Check for VMS path specs, and reject if still present.  - FM
     */
    if (StrChr(cp, ':') != NULL || StrChr(cp, ']') != NULL) {
	FREE(file);
	return (FALSE);
    }
#endif /* VMS */

    /*
     * Check for a URL or absolute path, and reject if present.  - FM
     */
    if (is_url(cp) || LYIsPathSep(*cp)) {
	FREE(file);
	return (FALSE);
    }

    /*
     * Simplify it.  - FM
     */
    HTSimplify(cp, FALSE);

    /*
     * Check if it has a pointless "./".  - FM
     */
    if (!StrNCmp(cp, "./", 2)) {
	if (StrChr((cp + 2), '/') == NULL) {
	    cp += 2;
	}
    }

    /*
     * Check for spoofing.  - FM
     */
    if (*cp == '\0'
	|| LYIsPathSep(*cp)
	|| LYIsPathSep(cp[(strlen(cp) - 1)])
	|| strstr(cp, "..") != NULL
	|| !strcmp(cp, ".")) {
	FREE(file);
	return (FALSE);
    }

    /*
     * Load what we have at this point into fbuffer, trimming if too long, and
     * claim it's OK.  - FM
     */
    if (fbuffer_size > 3 && StrNCmp(cp, "./", 2) && StrChr(cp, '/')) {
	/*
	 * We have a subdirectory and no lead "./", so prefix it to make the
	 * situation clear.  - FM
	 */
	strcpy(fbuffer, "./");
	if (strlen(cp) > (fbuffer_size - 3))
	    cp[(fbuffer_size - 3)] = '\0';
	strcat(fbuffer, cp);
    } else {
	if (strlen(cp) > (fbuffer_size - 1))
	    cp[(fbuffer_size - 1)] = '\0';
	strcpy(fbuffer, cp);
    }
    FREE(file);
    return (TRUE);
}

/*
 * Search for a leading tilde, optionally embedded.  If found, return a pointer
 * to the tilde.  If not found, return the original parameter.
 */
static char *FindLeadingTilde(char *pathname, int embedded)
{
    char *result = pathname;

    if (pathname != NULL) {
	if (embedded) {
	    while (pathname[0] != '\0') {
		if (LYIsPathSep(pathname[0])) {
		    if (LYIsTilde(pathname[1])) {
			++pathname;
			break;
		    }
		}
		++pathname;
	    }
	}
	if (LYIsTilde(*pathname))
	    result = pathname;
    }
    return result;
}

/*
 * Convert a non-absolute path to one which is off the home directory.  Expand
 * tildes as a side-effect.  Return a pointer to the converted result.
 */
char *LYAbsOrHomePath(char **fname)
{
    if (*fname && !LYisAbsPath(*fname)) {
	if (LYIsTilde((*fname)[0])) {
	    LYTildeExpand(fname, FALSE);
	} else {
	    char temp[LY_MAXPATH];

	    LYAddPathToHome(temp, sizeof(temp), *fname);
	    StrAllocCopy(*fname, temp);
	}
    }
    return *fname;
}

/*
 * Expand a "leading" tilde into the user's home directory in WWW format.  If
 * "embedded" is true, allow that "leading" tilde to follow a path separator.
 */
char *LYTildeExpand(char **pathname,
		    int embedded)
{
    char *temp = FindLeadingTilde(*pathname, embedded);

    if (LYIsTilde(temp[0])) {

	CTRACE((tfp, "LYTildeExpand %s\n", *pathname));
	if (LYIsPathSep(temp[1])) {
	    char *first = NULL;
	    char *second = NULL;

	    StrAllocCopy(first, *pathname);
	    first[temp - *pathname] = '\0';

	    StrAllocCopy(second, temp + 2);

	    StrAllocCopy(*pathname, first);
	    StrAllocCat(*pathname, wwwName(Home_Dir()));
	    LYAddPathSep(pathname);
	    StrAllocCat(*pathname, second);

	    FREE(first);
	    FREE(second);
	} else if (temp[1] == '\0') {
	    StrAllocCopy(*pathname, wwwName(Home_Dir()));
#ifndef NOUSERS
	} else {
	    char *save;
	    char saved = '\0';
	    struct passwd *pw;

	    for (save = temp; *save != '\0'; ++save) {
		if (LYIsPathSep(*save)) {
		    saved = *save;
		    *save = '\0';
		    break;
		}
	    }
	    pw = getpwnam(temp + 1);
	    *save = saved;
	    if (pw != 0 && non_empty(pw->pw_dir)) {
		temp = NULL;
		StrAllocCopy(temp, save);
		StrAllocCopy(*pathname, pw->pw_dir);
		StrAllocCat(*pathname, temp);
		FREE(temp);
	    }
#endif
	}
	CTRACE((tfp, "expanded path %s\n", *pathname));
    }
    return *pathname;
}

/*
 * This function appends fname to the home path and returns the full path and
 * filename.  The fname string can be just a filename (e.g.,
 * "lynx_bookmarks.html"), or include a subdirectory off the home directory, in
 * which case fname should begin with "./" (e.g., ./BM/lynx_bookmarks.html) Use
 * LYPathOffHomeOK() to check and/or fix up fname before calling this function. 
 * On VMS, the resultant full path and filename are converted to VMS syntax.  -
 * FM
 */
void LYAddPathToHome(char *fbuffer,
		     size_t fbuffer_size,
		     const char *fname)
{
    char *home = NULL;
    const char *file = fname;
    int len;

    /*
     * Make sure we have a buffer.  - FM
     */
    if (!fbuffer)
	return;
    if (fbuffer_size < 2) {
	fbuffer[0] = '\0';
	return;
    }
    fbuffer[(fbuffer_size - 1)] = '\0';

    /*
     * Make sure we have a file name.  - FM
     */
    if (!file)
	file = "";

    /*
     * Set up home string and length.  - FM
     */
    StrAllocCopy(home, Home_Dir());

#ifdef VMS
#define NO_HOMEPATH "Error:"
#else
#define NO_HOMEPATH "/error"
#endif /* VMS */
    if (!non_empty(home))
	/*
	 * Home_Dir() has a bug if this ever happens.  - FM
	 */
	StrAllocCopy(home, NO_HOMEPATH);

    len = (int) fbuffer_size - ((int) strlen(home) + 1);
    if (len <= 0) {
	/*
	 * Buffer is smaller than or only big enough for the home path.  Load
	 * what fits of the home path and return.  This will fail, but we need
	 * something in the buffer.  - FM
	 */
	LYStrNCpy(fbuffer, home, (fbuffer_size - 1));
	FREE(home);
	return;
    }
#ifdef VMS
    /*
     * Check whether we have a subdirectory path or just a filename.  - FM
     */
    if (!StrNCmp(file, "./", 2)) {
	/*
	 * We have a subdirectory path.  - FM
	 */
	if (home[strlen(home) - 1] == ']') {
	    /*
	     * We got the home directory, so convert it to SHELL syntax and
	     * append subdirectory path, then convert that to VMS syntax.  - FM
	     */
	    char *temp = NULL;

	    HTSprintf0(&temp, "%s%s", HTVMS_wwwName(home), (file + 1));
	    sprintf(fbuffer, "%.*s",
		    (fbuffer_size - 1), HTVMS_name("", temp));
	    FREE(temp);
	} else {
	    /*
	     * This will fail, but we need something in the buffer.  - FM
	     */
	    sprintf(fbuffer, "%s%.*s", home, len, file);
	}
    } else {
	/*
	 * We have a file in the home directory.  - FM
	 */
	sprintf(fbuffer, "%s%.*s", home, len, file);
    }
#else
    /*
     * Check whether we have a subdirectory path or just a filename.  - FM
     */
    sprintf(fbuffer, "%s/%.*s", home, len,
	    (StrNCmp(file, "./", 2) ? file : (file + 2)));
#endif /* VMS */
    FREE(home);
}

/*
 * Given a filename, concatenate it to the save-space pathname, unless it is
 * an absolute pathname.  If there is no save-space defined, use the home
 * directory. Return a new string with the result.
 */
char *LYAddPathToSave(char *fname)
{
    char *result = NULL;

    if (LYisAbsPath(fname)) {
	StrAllocCopy(result, fname);
    } else {
	if (non_empty(lynx_save_space)) {
	    StrAllocCopy(result, lynx_save_space);
	} else {
	    char temp[LY_MAXPATH];

	    LYAddPathToHome(temp, sizeof(temp), fname);
	    StrAllocCopy(result, temp);
	}
    }
    return result;
}

#if !defined(HAVE_PUTENV) && !defined(_WINDOWS)
/*
 * No putenv on the NeXT so we use this code instead!
 */

/* Copyright (C) 1991 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can  redistribute it and/or
modify it under the terms of the GNU Library General  Public License as
published by the Free Software Foundation; either  version 2 of the
License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it  will be useful,
but WITHOUT ANY WARRANTY; without even the implied  warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library  General Public
License along with the GNU C Library; see the file  COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675  Mass Ave,
Cambridge, MA 02139, USA.  */

#if defined(STDC_HEADERS) || defined(USG)
#include <string.h>
#else /* Not (STDC_HEADERS or USG): */
#include <strings.h>
#endif /* STDC_HEADERS or USG */

#ifndef NULL
#define NULL 0
#endif /* !NULL */

extern char **environ;

/*
 * Put STRING, which is of the form "NAME=VALUE", in the environment.
 */
int putenv(const char *string)
{
    char *name_end = StrChr(string, '=');
    register size_t size;
    register char **ep;

    if (name_end == NULL) {
	/* Remove the variable from the environment.  */
	size = strlen(string);
	for (ep = environ; *ep != NULL; ++ep)
	    if (!StrNCmp(*ep, string, size) && (*ep)[size] == '=') {
		while (ep[1] != NULL) {
		    ep[0] = ep[1];
		    ++ep;
		}
		*ep = NULL;
		return 0;
	    }
    }

    size = 0;
    for (ep = environ; *ep != NULL; ++ep)
	if (!StrNCmp(*ep, string, name_end - string) &&
	    (*ep)[name_end - string] == '=')
	    break;
	else
	    ++size;

    if (*ep == NULL) {
	static char **last_environ = NULL;
	char **new_environ = (char **) malloc((size + 2) * sizeof(char *));

	if (new_environ == NULL)
	    return -1;
	(void) memcpy((char *) new_environ, (char *) environ, size * sizeof(char *));

	new_environ[size] = (char *) string;
	new_environ[size + 1] = NULL;
	if (last_environ != NULL)
	    FREE(last_environ);
	last_environ = new_environ;
	environ = new_environ;
    } else
	*ep = (char *) string;

    return 0;
}
#endif /* !HAVE_PUTENV */

#ifdef NEED_REMOVE
int remove(char *name)
{
    return unlink(name);
}
#endif

#if defined(MULTI_USER_UNIX)

#if defined(HAVE_LSTAT) && defined(S_IFLNK)
/*
 * If IsOurFile() is checking a symbolic link, ensure that the target
 * points to the user's file as well.
 */
static BOOL IsOurSymlink(const char *name)
{
    BOOL result = FALSE;
    size_t size = LY_MAXPATH;
    size_t used;
    char *buffer = typeMallocn(char, (unsigned) size);
    char *check;

    if (buffer != 0) {
	while ((used = (size_t) readlink(name, buffer, (size - 1))) == size - 1) {
	    check = typeRealloc(char, buffer, (unsigned) (size *= 2));

	    if (check == 0)
		break;
	    buffer = check;
	}
	if (buffer != 0) {
	    if ((int) used > 0) {
		buffer[used] = '\0';
	    } else {
		FREE(buffer);
	    }
	}
    }
    if (buffer != 0) {
	if (!LYisAbsPath(buffer)) {
	    char *cutoff = LYLastPathSep(name);
	    char *clone = NULL;

	    if (cutoff != 0) {
		HTSprintf0(&clone, "%.*s%s%s",
			   (int) (cutoff - name),
			   name, FILE_SEPARATOR, buffer);
		FREE(buffer);
		buffer = clone;
	    }
	}
	CTRACE2(TRACE_CFG, (tfp, "IsOurSymlink(%s -> %s)\n", name, buffer));
	result = IsOurFile(buffer);
	FREE(buffer);
    }
    return result;
}
#endif

/*
 * Verify if this is really a file which is not accessed by a symbolic link,
 * except for the special case of its directory being pointed to by a link from
 * a directory owned by root and not writable by other users.
 */
BOOL IsOurFile(const char *name)
{
    BOOL result = FALSE;
    struct stat data;

    if (!LYIsTilde(name[0])
	&& lstat(name, &data) == 0
	&& ((S_ISREG(data.st_mode)
	     && (data.st_mode & (S_IWOTH | S_IWGRP)) == 0
	     && data.st_uid == getuid())
#if defined(HAVE_LSTAT) && defined(S_IFLNK)
	    || (S_ISLNK(data.st_mode) && IsOurSymlink(name))
#endif
	)) {
	int linked = FALSE;

	/*
	 * ( If this is not a single-user system, the other user is presumed by
	 * some people busy trying to use a symlink attack on our files ;-)
	 */
#if defined(HAVE_LSTAT)
	char *path = 0;
	char *leaf;

	StrAllocCopy(path, name);
	do {
	    if ((leaf = LYPathLeaf(path)) != path)
		*--leaf = '\0';	/* write a null on the '/' */
	    if (lstat(*path ? path : "/", &data) != 0) {
		break;
	    }
	    /*
	     * If we find a symbolic link, it has to be in a directory that's
	     * protected.  Otherwise someone could have switched it to point
	     * to one of the real user's files.
	     */
	    if (S_ISLNK(data.st_mode)) {
		linked = TRUE;	/* could be link-to-link; doesn't matter */
	    } else if (S_ISDIR(data.st_mode)) {
		if (linked) {
		    linked = FALSE;
		    /*
		     * We assume that a properly-configured system has the
		     * unwritable directories owned by root.  This is not
		     * necessarily so (bin, news, etc., may), but the only
		     * uid we can count on is 0.  It would be nice to add a
		     * check for the gid also, but that wouldn't be
		     * portable.
		     *
		     * Likewise, the t-bit would be nice to rely upon.  However
		     * it is marked as an extension in POSIX, rather than
		     * required.
		     */
		    if (data.st_uid != 0
			|| (data.st_mode & S_IWOTH) != 0) {
			linked = TRUE;	/* force an error-return */
			break;
		    }
		}
	    } else if (linked) {
		break;
	    }
	} while (leaf != path);
	FREE(path);
#endif
	result = (BOOLEAN) !linked;
    }
    CTRACE2(TRACE_CFG, (tfp, "IsOurFile(%s) %d\n", name, result));
    return result;
}

/*
 * Open a file that we don't want other users to see.
 */
static FILE *OpenHiddenFile(const char *name, const char *mode)
{
    FILE *fp = 0;
    struct stat data;
    BOOLEAN binary = (BOOLEAN) (StrChr(mode, 'b') != 0);

#if defined(O_CREAT) && defined(O_EXCL)		/* we have fcntl.h or kindred? */
    /*
     * This is the preferred method for creating new files, since it ensures
     * that no one has an existing file or link that they happen to own.
     */
    if (*mode == 'w') {
	int fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);

	if (fd < 0
	    && errno == EEXIST
	    && IsOurFile(name)) {
	    if (remove(name) == 0) {
		/* FIXME: there's a race at this point if directory is open */
		fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);
	    }
	}
	if (fd >= 0) {
#if defined(O_BINARY) && defined(__CYGWIN__)
	    if (binary)
		setmode(fd, O_BINARY);
#endif
	    fp = fdopen(fd, mode);
	}
    } else
#endif
    if (*mode == 'a') {
	if (IsOurFile(name)
	    && chmod(name, HIDE_CHMOD) == 0)
	    fp = fopen(name, mode);
	else if (lstat(name, &data) != 0)
	    fp = OpenHiddenFile(name, binary ? BIN_W : TXT_W);
	/*
	 * This is less stringent, but reasonably portable.  For new files, the
	 * umask will suffice; however if the file already exists we'll change
	 * permissions first, before opening it.  If the chmod fails because of
	 * some reason other than a non-existent file, there's no point in trying
	 * to open it.
	 *
	 * This won't work properly if the user is root, since the chmod succeeds.
	 */
    } else if (*mode != 'a') {
	mode_t save = umask(HIDE_UMASK);

	if (chmod(name, HIDE_CHMOD) == 0 || errno == ENOENT)
	    fp = fopen(name, mode);
	(void) umask(save);
    }
    return fp;
}
#else
#define OpenHiddenFile(name, mode) fopen(name, mode)
#endif /* MULTI_USER_UNIX */

FILE *LYNewBinFile(const char *name)
{
#ifdef VMS
    FILE *fp = fopen(name, BIN_W, "mbc=32");

    (void) chmod(name, HIDE_CHMOD);
#else
    FILE *fp = OpenHiddenFile(name, BIN_W);
#endif
    return fp;
}

FILE *LYNewTxtFile(const char *name)
{
    FILE *fp;

#ifdef VMS
    fp = fopen(name, TXT_W, "shr=get");
    (void) chmod(name, HIDE_CHMOD);
#else
    SetDefaultMode(O_TEXT);

    fp = OpenHiddenFile(name, TXT_W);

    SetDefaultMode(O_BINARY);
#endif

    return fp;
}

FILE *LYAppendToTxtFile(const char *name)
{
    FILE *fp;

#ifdef VMS
    fp = fopen(name, TXT_A, "shr=get");
    (void) chmod(name, HIDE_CHMOD);
#else
    SetDefaultMode(O_TEXT);

    fp = OpenHiddenFile(name, TXT_A);

    SetDefaultMode(O_BINARY);
#endif
    return fp;
}

#if defined(MULTI_USER_UNIX)
/*
 * Restore normal permissions to a copy of a file that we have created with
 * temp file restricted permissions.  The normal umask should apply for user
 * files.  - kw
 */
void LYRelaxFilePermissions(const char *name)
{
    mode_t mode;
    struct stat stat_buf;

    if (stat(name, &stat_buf) == 0 &&
	S_ISREG(stat_buf.st_mode) &&
	(mode = (stat_buf.st_mode & 0777)) == HIDE_CHMOD) {
	/*
	 * It looks plausible that this is a file we created with temp file
	 * paranoid permissions (and the umask wasn't even more restrictive
	 * when it was copied).  - kw
	 */
	mode_t save = umask(HIDE_UMASK);

	mode = (mode_t) (((mode & 0700) | 0066) & ~save);
	(void) umask(save);
	(void) chmod(name, mode);
    }
}
#endif

/*
 * Check if the given anchor has an associated file-cache.
 */
BOOLEAN LYCachedTemp(char *target,
		     char **cached)
{
    BOOLEAN result = FALSE;

    if (*cached) {
	LYStrNCpy(target, *cached, LY_MAXPATH);
	FREE(*cached);
	if (LYCanReadFile(target)) {
	    if (remove(target) != 0) {
		CTRACE((tfp, "cannot remove %s\n", target));
	    }
	}
	result = TRUE;
    }
    return result;
}

#ifndef HAVE_MKDTEMP
#define mkdtemp(path) ((mktemp(path) != 0) && (mkdir(path, 0700) == 0))
#endif

/*
 * Open a temp-file, ensuring that it is unique, and not readable by other
 * users.
 *
 * The mode can be one of: "w", "a", "wb".
 */
FILE *LYOpenTemp(char *result,
		 const char *suffix,
		 const char *mode)
{
    FILE *fp = 0;
    BOOL txt = TRUE;
    char wrt = 'r';
    LY_TEMP *p;

    CTRACE((tfp, "LYOpenTemp(,%s,%s)\n", suffix, mode));
    if (result == 0)
	return 0;

    while (*mode != '\0') {
	switch (*mode++) {
	case 'w':
	    wrt = 'w';
	    break;
	case 'a':
	    wrt = 'a';
	    break;
	case 'b':
	    txt = FALSE;
	    break;
	default:
	    CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
	    return 0;
	}
    }

    /*
     * Verify if the given space looks secure enough.  Otherwise, make a
     * secure subdirectory of that.
     */
#if defined(MULTI_USER_UNIX) && (defined(HAVE_MKTEMP) || defined(HAVE_MKDTEMP))
    if (lynx_temp_subspace == 0) {
	BOOL make_it = FALSE;
	struct stat sb;

	if (lstat(lynx_temp_space, &sb) == 0
	    && S_ISDIR(sb.st_mode)) {
	    if (sb.st_uid != getuid()
		|| (sb.st_mode & (S_IWOTH | S_IWGRP)) != 0) {
		make_it = TRUE;
		CTRACE((tfp,
			"lynx_temp_space is not our directory %s owner %d mode %03o\n",
			lynx_temp_space,
			(int) sb.st_uid,
			(unsigned) (sb.st_mode & 0777)));
	    }
	} else {
	    make_it = TRUE;
	    CTRACE((tfp, "lynx_temp_space is not a directory %s\n", lynx_temp_space));
	}
	if (make_it) {
	    mode_t old_mask = umask(HIDE_UMASK);

	    StrAllocCat(lynx_temp_space, "lynxXXXXXXXXXX");
	    if (mkdtemp(lynx_temp_space) == 0) {
		printf("%s: %s\n", lynx_temp_space, LYStrerror(errno));
		exit_immediately(EXIT_FAILURE);
	    }
	    (void) umask(old_mask);
	    lynx_temp_subspace = 1;
	    StrAllocCat(lynx_temp_space, "/");
	    CTRACE((tfp, "made subdirectory %s\n", lynx_temp_space));
	} else {
	    lynx_temp_subspace = -1;
	}
    }
#endif

    do {
	if (!fmt_tempname(result, lynx_temp_space, suffix))
	    return 0;
	if (txt) {
	    switch (wrt) {
	    case 'w':
		fp = LYNewTxtFile(result);
		break;
	    case 'a':
		fp = LYAppendToTxtFile(result);
		break;
	    }
	} else {
	    fp = LYNewBinFile(result);
	}
	/*
	 * If we get a failure to make a temporary file, don't bother to try a
	 * different name unless the failure was because the file already
	 * exists.
	 */
#ifdef EEXIST			/* FIXME (need a better test) in fcntl.h or unistd.h */
	if ((fp == 0) && (errno != EEXIST)) {
	    CTRACE((tfp, "... LYOpenTemp(%s) failed: %s\n",
		    result, LYStrerror(errno)));
	    return 0;
	}
#endif
    } while (fp == 0);

    if ((p = typecalloc(LY_TEMP)) != 0) {
	p->next = ly_temp;
	StrAllocCopy((p->name), result);
	p->file = fp;
	p->outs = (BOOLEAN) (wrt != 'r');
	ly_temp = p;
    } else {
	outofmem(__FILE__, "LYOpenTemp");
    }

    CTRACE((tfp, "... LYOpenTemp(%s)\n", result));
    return fp;
}

/*
 * Reopen a temporary file
 */
FILE *LYReopenTemp(char *name)
{
    LY_TEMP *p;
    FILE *fp = 0;

    LYCloseTemp(name);
    if ((p = FindTempfileByName(name)) != 0) {
	fp = p->file = LYAppendToTxtFile(name);
    }
    return fp;
}

/*
 * Open a temp-file for writing, possibly re-using a previously used
 * name and file.
 * If a non-empty fname is given, it is reused if it indicates a file
 * previously registered as a temp file and, in case the file still
 * exists, if it looks like we can write to it safely.  Otherwise a
 * new temp file (with new name) will be generated and returned in fname.
 *
 * File permissions are set so that the file is not readable by unprivileged
 * other users.
 *
 * Suffix is only used if fname is not being reused.
 * The mode should be "w", others are possible (they may be passed on)
 * but probably don't make sense. - kw
 */
FILE *LYOpenTempRewrite(char *fname,
			const char *suffix,
			const char *mode)
{
    FILE *fp = 0;
    BOOL txt = TRUE;
    char wrt = 'r';
    BOOL registered = NO;
    BOOL writable_exists = NO;
    BOOL is_ours = NO;
    BOOL still_open = NO;
    LY_TEMP *p;
    struct stat stat_buf;

    CTRACE((tfp, "LYOpenTempRewrite(%s,%s,%s)\n", fname, suffix, mode));
    if (*fname == '\0')		/* first time, no filename yet */
	return (LYOpenTemp(fname, suffix, mode));

    if ((p = FindTempfileByName(fname)) != 0) {
	registered = YES;
	if (p->file != 0)
	    still_open = YES;
	CTRACE((tfp, "...used before%s\n", still_open ? ", still open!" : "."));
    }

    if (registered) {
#ifndef NO_GROUPS
	writable_exists = HTEditable(fname);	/* existing, can write */
#define CTRACE_EXISTS "exists and is writable, "
#else
	writable_exists = (BOOL) (stat(fname, &stat_buf) == 0);		/* existing, assume can write */
#define CTRACE_EXISTS "exists, "
#endif

	if (writable_exists) {
	    is_ours = IsOurFile(fname);
	}
	CTRACE((tfp, "...%s%s\n",
		writable_exists ? CTRACE_EXISTS : "",
		is_ours ? "is our file." : "is NOT our file."));
    }

    /*
     * Note that in cases where LYOpenTemp is called as fallback below, we
     * don't call LYRemoveTemp first.  That may be appropriate in some cases,
     * but not trying to remove a weird existing file seems safer and could
     * help diagnose an unusual situation.  (They may be removed anyway later.)
     */
    if (still_open) {
	/*
	 * This should probably not happen.  Make a new one.
	 */
	return (LYOpenTemp(fname, suffix, mode));
    } else if (!registered) {
	/*
	 * Not registered.  It should have been registered at one point though,
	 * otherwise we wouldn't be called like this.
	 */
	return (LYOpenTemp(fname, suffix, mode));
    } else if (writable_exists && !is_ours) {
	/*
	 * File exists, writable if we checked, but something is wrong with it.
	 */
	return (LYOpenTemp(fname, suffix, mode));
#ifndef NO_GROUPS
    } else if (!is_ours && (lstat(fname, &stat_buf) == 0)) {
	/*
	 * Exists but not writable, and something is wrong with it.
	 */
	return (LYOpenTemp(fname, suffix, mode));
#endif
    }

    while (*mode != '\0') {
	switch (*mode++) {
	case 'w':
	    wrt = 'w';
	    break;
	case 'a':
	    wrt = 'a';
	    break;
	case 'b':
	    txt = FALSE;
	    break;
	default:
	    CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
	    return fp;
	}
    }

    if (is_ours) {
	/*
	 * Yes, it exists, is writable if we checked, and everything looks ok
	 * so far.  This should be the most regular case.  - kw
	 */
#ifdef HAVE_TRUNCATE
	if (txt == TRUE) {	/* limitation of LYReopenTemp.  shrug */
	    /*
	     * We truncate and then append, this avoids having a small window
	     * in which the file doesn't exist.  - kw
	     */
	    if (truncate(fname, (off_t) 0) != 0) {
		CTRACE((tfp, "... truncate(%s,0) failed: %s\n",
			fname, LYStrerror(errno)));
		return (LYOpenTemp(fname, suffix, mode));
	    } else {
		return (LYReopenTemp(fname));
	    }
	}
#endif
	remove(fname);

    }

    /* We come here in two cases:  either the file existed and was ours and we
     * just got rid of it.  Or the file did and does not exist, but is
     * registered as a temp file.  It must have been removed by some means
     * other than LYRemoveTemp.  In both cases, reuse the name!  - kw
     */

    if (txt) {
	switch (wrt) {
	case 'w':
	    fp = LYNewTxtFile(fname);
	    break;
	case 'a':
	    fp = LYAppendToTxtFile(fname);
	    break;
	}
    } else {
	fp = LYNewBinFile(fname);
    }
    p->file = fp;

    CTRACE((tfp, "... LYOpenTempRewrite(%s), %s\n", fname,
	    (fp) ? "ok" : "failed"));
    /*
     * We could fall back to trying LYOpenTemp() here in case of failure. 
     * After all the checks already done above a filure here should be pretty
     * unusual though, so maybe it's better to let the user notice that
     * something went wrong, and not try to fix it up.  - kw
     */
    return fp;
}

/*
 * Special case of LYOpenTemp, used for manipulating bookmark file, i.e., with
 * renaming.
 */
FILE *LYOpenScratch(char *result,
		    const char *prefix)
{
    FILE *fp;
    LY_TEMP *p;

    if (!fmt_tempname(result, prefix, HTML_SUFFIX))
	return 0;

    if ((fp = LYNewTxtFile(result)) != 0) {
	if ((p = typecalloc(LY_TEMP)) != 0) {
	    p->next = ly_temp;
	    StrAllocCopy((p->name), result);
	    p->file = fp;
	    ly_temp = p;
	} else {
	    outofmem(__FILE__, "LYOpenScratch");
	}
    }
    CTRACE((tfp, "LYOpenScratch(%s)\n", result));
    return fp;
}

static void LY_close_temp(LY_TEMP * p)
{
    if (p->file != 0) {
	if (p->outs) {
	    LYCloseOutput(p->file);
	} else {
	    LYCloseInput(p->file);
	}
	p->file = 0;
    }
}

/*
 * Close a temp-file, given its name
 */
void LYCloseTemp(char *name)
{
    LY_TEMP *p;

    CTRACE((tfp, "LYCloseTemp(%s)\n", name));
    if ((p = FindTempfileByName(name)) != 0) {
	CTRACE((tfp, "...LYCloseTemp(%s)%s\n", name,
		(p->file != 0) ? ", closed" : ""));
	LY_close_temp(p);
    }
}

/*
 * Close a temp-file, given its file-pointer
 */
void LYCloseTempFP(FILE *fp)
{
    LY_TEMP *p;

    CTRACE((tfp, "LYCloseTempFP\n"));
    if ((p = FindTempfileByFP(fp)) != 0) {
	LY_close_temp(p);
	CTRACE((tfp, "...LYCloseTempFP(%s)\n", p->name));
    }
}

/*
 * Close a temp-file, removing it.
 */
int LYRemoveTemp(char *name)
{
    LY_TEMP *p, *q;
    int code = -1;

    if (non_empty(name)) {
	CTRACE((tfp, "LYRemoveTemp(%s)\n", name));
	for (p = ly_temp, q = 0; p != 0; q = p, p = p->next) {
	    if (!strcmp(name, p->name)) {
		if (q != 0) {
		    q->next = p->next;
		} else {
		    ly_temp = p->next;
		}
		LY_close_temp(p);
		code = HTSYS_remove(name);
		CTRACE((tfp, "...LYRemoveTemp done(%d)%s\n", code,
			(p->file != 0) ? ", closed" : ""));
		CTRACE_FLUSH(tfp);
		FREE(p->name);
		FREE(p);
		break;
	    }
	}
    }
    return code;
}

/*
 * Remove all of the temp-files.  Note that this assumes that they are closed,
 * since some systems will not allow us to remove a file which is open.
 */
void LYCleanupTemp(void)
{
    while (ly_temp != 0) {
	(void) LYRemoveTemp(ly_temp->name);
    }
#if defined(MULTI_USER_UNIX)
    if (lynx_temp_subspace > 0) {
	char result[LY_MAXPATH];

	LYStrNCpy(result, lynx_temp_space, sizeof(result) - 1);
	LYTrimPathSep(result);
	CTRACE((tfp, "LYCleanupTemp removing %s\n", result));
	rmdir(result);
	lynx_temp_subspace = -1;
    }
#endif
}

/*
 * We renamed a temporary file.  Keep track so we can remove it on exit.
 */
void LYRenamedTemp(char *oldname,
		   char *newname)
{
    LY_TEMP *p;

    CTRACE((tfp, "LYRenamedTemp(old=%s, new=%s)\n", oldname, newname));
    if ((p = FindTempfileByName(oldname)) != 0) {
	StrAllocCopy((p->name), newname);
    }
}

#ifndef DISABLE_BIBP
/*
 * Check that bibhost defines the BibP icon.
 */
void LYCheckBibHost(void)
{
    DocAddress bibhostIcon;
    BOOLEAN saveFlag;

    bibhostIcon.address = NULL;
    StrAllocCopy(bibhostIcon.address, BibP_bibhost);
    StrAllocCat(bibhostIcon.address, "bibp1.0/bibpicon.jpg");
    bibhostIcon.post_data = NULL;
    bibhostIcon.post_content_type = NULL;
    bibhostIcon.bookmark = FALSE;
    bibhostIcon.isHEAD = FALSE;
    bibhostIcon.safe = FALSE;
    saveFlag = traversal;
    traversal = TRUE;		/* Hack to force error response. */
    BibP_bibhost_available = (BOOLEAN) (HTLoadAbsolute(&bibhostIcon) == YES);
    traversal = saveFlag;
    BibP_bibhost_checked = TRUE;
}
#endif /* !DISABLE_BIBP */

/*
 * Management of User Interface Pages.  - kw
 *
 * These are mostly temp files.  Pages which can be recognized by their special
 * URL (after having been loaded) need not be tracked here.
 *
 * First some private stuff:
 */
typedef struct uipage_entry {
    UIP_t type;
    unsigned flags;
    char *url;
    HTList *alturls;
    char *file;
} uip_entry;

#define UIP_F_MULTI	0x0001	/* flag: track multiple instances */
#define UIP_F_LIMIT	0x0002	/* flag: limit size of alturls list */
#define UIP_F_LMULTI   (UIP_F_MULTI | UIP_F_LIMIT)
/* *INDENT-OFF* */
static uip_entry ly_uip[] =
{
    { UIP_HISTORY		, UIP_F_LMULTI, NULL, NULL, NULL }
  , { UIP_DOWNLOAD_OPTIONS	, 0	      , NULL, NULL, NULL }
  , { UIP_PRINT_OPTIONS		, 0	      , NULL, NULL, NULL }
  , { UIP_SHOWINFO		, UIP_F_LMULTI, NULL, NULL, NULL }
  , { UIP_LIST_PAGE		, UIP_F_LMULTI, NULL, NULL, NULL }
  , { UIP_VLINKS		, UIP_F_LMULTI, NULL, NULL, NULL }
#if !defined(NO_OPTION_FORMS)
  , { UIP_OPTIONS_MENU		, UIP_F_LMULTI, NULL, NULL, NULL }
#endif
#ifdef DIRED_SUPPORT
  , { UIP_DIRED_MENU		, 0	      , NULL, NULL, NULL }
  , { UIP_PERMIT_OPTIONS	, 0	      , NULL, NULL, NULL }
  , { UIP_UPLOAD_OPTIONS	, UIP_F_LMULTI, NULL, NULL, NULL }
#endif
#ifdef USE_ADDRLIST_PAGE
  , { UIP_ADDRLIST_PAGE		, UIP_F_LMULTI, NULL, NULL, NULL }
#endif
  , { UIP_LYNXCFG		, UIP_F_LMULTI, NULL, NULL, NULL }
#if !defined(NO_CONFIG_INFO)
  , { UIP_CONFIG_DEF		, UIP_F_LMULTI, NULL, NULL, NULL }
#endif
/* The following are not generated tempfiles: */
  , { UIP_TRACELOG		, 0	     , NULL, NULL, NULL }
#if defined(DIRED_SUPPORT) && defined(OK_INSTALL)
  , { UIP_INSTALL		, 0	     , NULL, NULL, NULL }
#endif

};
/* *INDENT-ON* */

/*  Public entry points for User Interface Page management: */

BOOL LYIsUIPage3(const char *url,
		 UIP_t type,
		 int flagparam)
{
    unsigned int i;
    size_t l;
    BOOL result = NO;

    if (url) {
	for (i = 0; i < TABLESIZE(ly_uip); i++) {
	    if (ly_uip[i].type == type) {
		if (!ly_uip[i].url) {
		    break;
		} else if ((flagparam & UIP_P_FRAG) ?
			   (!StrNCmp(ly_uip[i].url, url, (l = strlen(ly_uip[i].url)))
			    && (url[l] == '\0' || url[l] == '#')) :
			   !strcmp(ly_uip[i].url, url)) {
		    result = YES;
		} else if (ly_uip[i].flags & UIP_F_MULTI) {
		    char *p;
		    HTList *l0 = ly_uip[i].alturls;

		    while ((p = (char *) HTList_nextObject(l0)) != NULL) {
			if ((flagparam & UIP_P_FRAG)
			    ? (!StrNCmp(p, url, (l = strlen(p))) &&
			       (url[l] == '\0' || url[l] == '#'))
			    : !strcmp(p, url)) {
			    result = YES;
			    break;
			}
		    }
		}
		break;
	    }
	}
    }
    return result;
}

void LYRegisterUIPage(const char *url,
		      UIP_t type)
{
    unsigned int i;

    for (i = 0; i < TABLESIZE(ly_uip); i++) {
	if (ly_uip[i].type == type) {
	    if (ly_uip[i].url && url &&
		!strcmp(ly_uip[i].url, url)) {

	    } else if (!ly_uip[i].url || !url ||
		       !(ly_uip[i].flags & UIP_F_MULTI)) {
		StrAllocCopy(ly_uip[i].url, url);

	    } else {
		char *p;
		int n = 0;
		HTList *l0 = ly_uip[i].alturls;

		while ((p = (char *) HTList_nextObject(l0)) != NULL) {
		    if (!strcmp(p, url))
			return;
		    if (!strcmp(p, ly_uip[i].url)) {
			StrAllocCopy(ly_uip[i].url, url);
			return;
		    }
		    n++;
		}
		if (!ly_uip[i].alturls)
		    ly_uip[i].alturls = HTList_new();

		if (n >= HTCacheSize && (ly_uip[i].flags & UIP_F_LIMIT))
		    HTList_removeFirstObject(ly_uip[i].alturls);
		HTList_addObject(ly_uip[i].alturls, ly_uip[i].url);
		ly_uip[i].url = NULL;
		StrAllocCopy(ly_uip[i].url, url);
	    }

	    return;
	}
    }
}

void LYUIPages_free(void)
{
    unsigned int i;

    for (i = 0; i < TABLESIZE(ly_uip); i++) {
	FREE(ly_uip[i].url);
	FREE(ly_uip[i].file);
	LYFreeStringList(ly_uip[i].alturls);
	ly_uip[i].alturls = NULL;
    }
}

/*
 * Convert local pathname to www name
 * (do not bother about file://localhost prefix at this point).
 */
const char *wwwName(const char *pathname)
{
    const char *cp = NULL;

#if defined(USE_DOS_DRIVES)
    cp = HTDOS_wwwName(pathname);
#else
#ifdef VMS
    cp = HTVMS_wwwName(pathname);
#else
    cp = pathname;
#endif /* VMS */
#endif

    return cp;
}

/*
 * Given a user-specified filename, e.g., for download or print, validate and
 * expand it.  Expand home-directory expressions in the given string.  Only
 * allow pipes if the user can spawn shell commands.
 */
BOOLEAN LYValidateFilename(bstring **result,
			   bstring **given)
{
    BOOLEAN code = TRUE;
    char *cp = NULL;

    /*
     * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path on VMS. 
     * - FM
     */
    if (LYIsNullDevice((*given)->str)) {
	/* just ignore it */
	code = FALSE;
#ifdef HAVE_POPEN
    } else if (LYIsPipeCommand((*given)->str)) {
	if (no_shell) {
	    HTUserMsg(SPAWNING_DISABLED);
	    code = FALSE;
	} else {
	    BStrCopy(*result, (*given));
	}
#endif
    } else {
	if (FindLeadingTilde((*given)->str, TRUE) != 0) {
	    char *cp1 = NULL;

	    StrAllocCopy(cp1, (*given)->str);
	    LYTildeExpand(&cp1, TRUE);
	    BStrCopy0(*result, cp1);
	    BStrCopy0(*given, cp1);
	    FREE(cp1);
	}
#ifdef VMS
	if (StrChr((*given)->str, '/') != NULL) {
	    BStrCopy0(*result, HTVMS_name("", (*given)->str));
	    BStrCopy(*given, *result);
	}
	if ((*given)->str[0] != '/'
	    && StrChr((*given)->str, ':') == NULL) {
	    BStrCopy0(*result, "sys$disk:");
	    if (StrChr((*given)->str, ']') == NULL)
		BStrCat0(*result, "[]");
	    BStrCat(*result, (*given));
	} else {
	    BStrCopy(*result, (*given));
	}
#else

#ifndef __EMX__
	if (!LYisAbsPath((*given)->str)) {
#if defined(__DJGPP__) || defined(_WINDOWS)
	    if (StrChr((*result)->str, ':') != NULL)
		cp = NULL;
	    else
#endif /*  __DJGPP__ || _WINDOWS */
	    {
#ifdef SUPPORT_CHDIR
		static char buf[LY_MAXPATH];

		cp = Current_Dir(buf);
#else
		cp = original_dir;
#endif
	    }
	} else
#endif /* __EMX__ */
	    cp = NULL;

	if (cp) {
	    LYTrimPathSep(cp);
	    BStrCopy0(*result, cp);
	    BStrCat0(*result, "/");
	} else {
	    BStrCopy0(*result, "");
	}
	if (code) {
	    cp = HTSYS_name((*given)->str);
	    BStrCat0(*result, cp);
	}
#endif /* VMS */
    }
    return code;
}

/*
 * Given a valid filename, check if it exists.  If so, we'll have to worry
 * about overwriting it.
 *
 * Returns:
 *	'Y' (yes/success)
 *	'N' (no/retry)
 *	3   (cancel)
 */
int LYValidateOutput(char *filename)
{
    int c;

    CTRACE((tfp, "LYValidateOutput '%s'\n", filename));

    /*
     * Assume we can write to a pipe
     */
#ifdef HAVE_POPEN
    if (LYIsPipeCommand(filename))
	return 'Y';
#endif

    if (no_dotfiles || !show_dotfiles) {
	if (*LYPathLeaf(filename) == '.') {
	    HTAlert(FILENAME_CANNOT_BE_DOT);
	    return 'N';
	}
    }

    /*
     * See if it already exists.
     */
    if (LYCanReadFile(filename)) {
#ifdef VMS
	c = HTConfirm(FILE_EXISTS_HPROMPT);
#else
	c = HTConfirm(FILE_EXISTS_OPROMPT);
#endif /* VMS */
	if (HTLastConfirmCancelled()) {
	    HTInfoMsg(SAVE_REQUEST_CANCELLED);
	    return 3;
	} else if (c == NO) {
	    return 'N';
	}
    } else if (!LYCanWriteFile(filename)) {
	return 'N';
    }
    return 'Y';
}

/*
 * Convert a local filename to a URL
 */
void LYLocalFileToURL(char **target,
		      const char *source)
{
    const char *leaf;

    StrAllocCopy(*target, "file://localhost");

    leaf = wwwName(source);

    if (!LYisAbsPath(source)) {
	char temp[LY_MAXPATH];

	Current_Dir(temp);
	if (!LYIsHtmlSep(*temp))
	    LYAddHtmlSep(target);
	StrAllocCat(*target, temp);
    }
    if (leaf && !LYIsHtmlSep(*leaf))
	LYAddHtmlSep(target);
    StrAllocCat(*target, leaf);
}

#define PUT_STRING(buf)    (*(target)->isa->put_string)(target, buf)

/*
 * Like WriteInternalTitle, used for writing title on pages constructed via
 * streams.
 */
void WriteStreamTitle(HTStream *target, const char *Title)
{
    char *buf = 0;

    PUT_STRING(LYNX_DOCTYPE);
    PUT_STRING("<html>\n<head>\n");
    LYAddMETAcharsetToStream(target, -1);
    HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n", Title);
    PUT_STRING(buf);
    FREE(buf);
}

/*
 * Open a temporary file for internal-pages, optionally reusing an existing
 * filename.
 */
FILE *InternalPageFP(char *filename,
		     int reuse_flag)
{
    FILE *fp;

    if (LYReuseTempfiles && reuse_flag) {
	fp = LYOpenTempRewrite(filename, HTML_SUFFIX, BIN_W);
    } else {
	(void) LYRemoveTemp(filename);
	fp = LYOpenTemp(filename, HTML_SUFFIX, BIN_W);
    }
    if (fp == NULL) {
	HTAlert(CANNOT_OPEN_TEMP);
    }
    return fp;
}

/*
 * This part is shared by all internal pages.
 */
void WriteInternalTitle(FILE *fp0, const char *Title)
{
    fprintf(fp0, LYNX_DOCTYPE);

    fprintf(fp0, "<html>\n<head>\n");
    LYAddMETAcharsetToFD(fp0, -1);
    if (LYIsListpageTitle(Title)) {
	if (StrChr(HTLoadedDocumentURL(), '"') == NULL) {
	    char *Address = NULL;

	    /*
	     * Insert a BASE tag so there is some way to relate the List Page
	     * file to its underlying document after we are done.  It won't be
	     * actually used for resolving relative URLs.  - kw
	     */
	    StrAllocCopy(Address, HTLoadedDocumentURL());
	    LYEntify(&Address, FALSE);
	    fprintf(fp0, "<base href=\"%s\">\n", Address);
	    FREE(Address);
	}
    }
    fprintf(fp0, "<title>%s</title>\n</head>\n<body>\n", Title);
}

/*
 * This is used to start most internal pages, except for special cases where
 * the embedded HREF's in the title differ.
 */
void BeginInternalPage(FILE *fp0, const char *Title,
		       const char *HelpURL)
{
    WriteInternalTitle(fp0, Title);

    if ((user_mode == NOVICE_MODE)
	&& LYwouldPush(Title, NULL)
	&& (HelpURL != 0)) {
	fprintf(fp0, "<h1>%s (%s%s%s), <a href=\"%s%s\">help</a></h1>\n",
		Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION,
		helpfilepath, HelpURL);
    } else {
	fprintf(fp0, "<h1>%s (%s%s%s)</h1>\n",
		Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION);
    }
}

void EndInternalPage(FILE *fp0)
{
    fprintf(fp0, "</body>\n</html>");
}

char *trimPoundSelector(char *address)
{
    char *pound = findPoundSelector(address);

    if (pound != 0)
	*pound = '\0';
    return pound;
}

/*
 * Trim a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to local filesystems.
 */
void LYTrimPathSep(char *path)
{
    size_t len;

    if (path != 0
	&& (len = strlen(path)) != 0
	&& LYIsPathSep(path[len - 1]))
	path[len - 1] = 0;
}

/*
 * Add a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to local filesystems.
 */
void LYAddPathSep(char **path)
{
    size_t len;
    char *temp;

    if ((path != 0)
	&& ((temp = *path) != 0)
	&& (len = strlen(temp)) != 0
	&& !LYIsPathSep(temp[len - 1])) {
	StrAllocCat(*path, FILE_SEPARATOR);
    }
}

/*
 * Add a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to local filesystems.
 */
void LYAddPathSep0(char *path)
{
    size_t len;

    if ((path != 0)
	&& (len = strlen(path)) != 0
	&& (len < LY_MAXPATH - 2)
	&& !LYIsPathSep(path[len - 1])) {
	strcat(path, FILE_SEPARATOR);
    }
}

/*
 * Check if a given string contains a path separator
 */
char *LYLastPathSep(const char *path)
{
    char *result;

#if defined(USE_DOS_DRIVES)
    if ((result = strrchr(path, '\\')) == 0)
	result = strrchr(path, '/');
#else
    result = strrchr(path, '/');
#endif
    return result;
}

/*
 * Trim a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to HTML paths.
 */
void LYTrimHtmlSep(char *path)
{
    size_t len;

    if (path != 0
	&& (len = strlen(path)) != 0
	&& LYIsHtmlSep(path[len - 1]))
	path[len - 1] = 0;
}

/*
 * Add a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to HTML paths.
 */
void LYAddHtmlSep(char **path)
{
    size_t len;
    char *temp;

    if ((path != 0)
	&& ((temp = *path) != 0)
	&& (len = strlen(temp)) != 0
	&& !LYIsHtmlSep(temp[len - 1])) {
	StrAllocCat(*path, "/");
    }
}

/*
 * Add a trailing path-separator to avoid confusing other programs when we concatenate
 * to it.  This only applies to HTML paths.
 */
void LYAddHtmlSep0(char *path)
{
    size_t len;

    if ((path != 0)
	&& (len = strlen(path)) != 0
	&& (len < LY_MAXPATH - 2)
	&& !LYIsHtmlSep(path[len - 1])) {
	strcat(path, "/");
    }
}

/*
 * Rename a file
 */
int LYRenameFile(char *src,
		 char *dst)
{
#ifdef _WINDOWS
    /* 
     * If dest_file exists prior to calling rename(), rename() will fail on Windows platforms.
     * https://www.securecoding.cert.org/confluence/display/c/FIO10-C.+Take+care+when+using+the+rename%28%29+function
     */
    struct stat st;

    if (stat(dst, &st) == 0) {
	unlink(dst);
    }
#endif
    return rename(src, dst);
}

/*
 * Copy a file
 */
int LYCopyFile(char *src,
	       char *dst)
{
    int code;
    const char *program;

    if ((program = HTGetProgramPath(ppCOPY)) != NULL) {
	char *the_command = 0;

	HTAddParam(&the_command, COPY_COMMAND, 1, program);
	HTAddParam(&the_command, COPY_COMMAND, 2, src);
	HTAddParam(&the_command, COPY_COMMAND, 3, dst);
	HTEndParam(&the_command, COPY_COMMAND, 3);

	CTRACE((tfp, "command: %s\n", the_command));
	stop_curses();
	code = LYSystem(the_command);
	start_curses();

	FREE(the_command);
    } else {
	FILE *fin, *fout;
	unsigned char buff[BUFSIZ];
	size_t len;

	code = EOF;
	if ((fin = fopen(src, BIN_R)) != 0) {
	    if ((fout = fopen(dst, BIN_W)) != 0) {
		code = 0;
		while ((len = fread(buff, (size_t) 1, sizeof(buff), fin)) != 0) {
		    if (fwrite(buff, (size_t) 1, len, fout) < len
			|| ferror(fout)) {
			code = EOF;
			break;
		    }
		}
		LYCloseOutput(fout);
	    }
	    LYCloseInput(fin);
	}
	CTRACE((tfp, "builtin copy ->%d\n\tsource=%s\n\ttarget=%s\n",
		code, src, dst));
    }

    if (code) {
	HTAlert(CANNOT_WRITE_TO_FILE);
    }
    return code;
}

#ifdef __DJGPP__
static char *escape_backslashes(char *source)
{
    char *result = 0;
    int count = 0;
    int n;

    for (n = 0; source[n] != '\0'; ++n) {
	if (source[n] == '\\')
	    ++count;
    }
    if (count != 0) {
	result = malloc(count + n + 1);
	if (result != 0) {
	    int ch;
	    char *target = result;

	    while ((ch = *source++) != '\0') {
		if (ch == '\\')
		    *target++ = ch;
		*target++ = ch;
	    }
	    *target = '\0';
	}
    }
    return result;
}
#endif /* __DJGPP__ */
/*
 * Invoke a shell command, return nonzero on error.
 */
int LYSystem(char *command)
{
    int code;
    int do_free = 0;

#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
    struct sigaction saved_sigtstp_act;
    BOOLEAN sigtstp_saved = FALSE;
#endif
    int saved_errno = 0;

#ifdef __EMX__
    int scrsize[4];
#endif

    fflush(stdout);
    fflush(stderr);
    CTRACE((tfp, "LYSystem(%s)\n", command));
    CTRACE_FLUSH(tfp);

#ifdef __DJGPP__
    __djgpp_set_ctrl_c(0);
    _go32_want_ctrl_break(1);
#endif /* __DJGPP__ */

#ifdef VMS
    code = DCLsystem(command);
#else
#  ifdef __EMX__		/* FIXME: Should be LY_CONVERT_SLASH? */
    /* Configure writes commands which contain direct slashes.
       Native command-(non)-shell will not tolerate this. */
    {
	char *space = command, *slash = command;

	_scrsize(scrsize);
	while (*space && *space != ' ' && *space != '\t')
	    space++;
	while (slash < space && *slash != '/')
	    slash++;
	if (slash != space) {
	    char *old = command;

	    command = NULL;
	    StrAllocCopy(command, old);
	    do_free = 1;
	    slash = (slash - old) + command - 1;
	    space = (space - old) + command;
	    while (++slash < space)
		if (*slash == '/')
		    *slash = '\\';
	}
    }
#  endif

    /*
     * This chunk of code does not work, for two reasons:
     * a) the Cygwin system() function edits out the backslashes
     * b) it does not account for more than one parameter, e.g., +number
     */
#if defined(__CYGWIN__) && defined(DOSPATH)	/* 1999/02/26 (Fri) */
    {
	char cmd[LY_MAXPATH];
	char win32_name[LY_MAXPATH];
	char new_cmd[LY_MAXPATH];
	char new_command[LY_MAXPATH * 2 + 10];
	char *p, *q;

	p = command;
	q = cmd;
	while (*p) {
	    if (*p == ' ')
		break;
	    else
		*q = *p;
	    p++;
	    q++;
	}
	*q = '\0';

	if (cmd[0] == '/')
	    cygwin_conv_to_full_posix_path(cmd, new_cmd);
	else
	    strcpy(new_cmd, cmd);

	while (*p == ' ')
	    p++;

	if (StrChr(p, '\\') == NULL) {
	    /* for Windows Application */
	    cygwin_conv_to_full_win32_path(p, win32_name);
	    sprintf(new_command, "%.*s \"%.*s\"",
		    LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
	} else {
	    /* for DOS like editor */
	    q = win32_name;
	    while (*p) {
		if (*p == '\\') {
		    if (*(p + 1) == '\\')
			p++;
		}
		*q = *p;
		q++, p++;
	    }
	    *q = '\0';
	    sprintf(new_command, "%.*s %.*s", LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
	}
	command = new_command;
    }
#endif

#ifdef __DJGPP__
    if (dj_is_bash) {
	char *new_command = escape_backslashes(command);

	if (new_command != 0) {
	    if (do_free)
		free(command);
	    command = new_command;
	}
    }
#endif /* __DJGPP__ */

#ifdef _WIN_CC
    code = exec_command(command, TRUE);		/* Wait exec */
#else /* !_WIN_CC */
#ifdef SIGPIPE
    if (restore_sigpipe_for_children)
	signal(SIGPIPE, SIG_DFL);	/* Some commands expect the default */
#endif
#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
    if (!dump_output_immediately && !LYCursesON && !no_suspend)
	sigtstp_saved = LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 1);
#endif
    code = system(command);
    saved_errno = errno;
#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
    if (sigtstp_saved)
	LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 0);
#endif
#ifdef SIGPIPE
    if (restore_sigpipe_for_children)
	signal(SIGPIPE, SIG_IGN);	/* Ignore it again - kw */
#endif
#endif
#endif

#ifdef __DJGPP__
    __djgpp_set_ctrl_c(1);
    _go32_want_ctrl_break(0);
#endif /* __DJGPP__ */

    fflush(stdout);
    fflush(stderr);

    if (do_free)
	FREE(command);
#if !defined(UCX) || !defined(VAXC)	/* errno not modifiable ?? */
    set_errno(saved_errno);	/* may have been clobbered */
#endif
#ifdef __EMX__			/* Check whether the screen size changed */
    size_change(0);
#endif
    return code;
}

/*
 * Return a string which can be used in LYSystem() for spawning a subshell
 */
#if defined(__CYGWIN__)		/* 1999/02/26 (Fri) */
int Cygwin_Shell(void)
{
    char *shell;
    int code;
    STARTUPINFO startUpInfo;
    PROCESS_INFORMATION procInfo;
    SECURITY_ATTRIBUTES sa;

    /* Set up security attributes to allow inheritance of the file handle */

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = 0;
    sa.bInheritHandle = TRUE;

    /* Init a startup structure */
    GetStartupInfo(&startUpInfo);

    shell = LYGetEnv("COMSPEC");

    /* Create the child process, specifying
       inherited handles. Pass the value of the
       handle as a command line parameter */
    code = 0;
    if (shell) {
	code = CreateProcess(0, shell, 0, 0,
			     TRUE, 0,
			     0, 0, &startUpInfo, &procInfo);

	if (!code) {
	    printf("shell = [%s], code = %ld\n", shell, (long) GetLastError());
	}

	/* wait for the child to return (this is not a requirement
	   since the child is its own independent process) */
	WaitForSingleObject(procInfo.hProcess, INFINITE);
    }

    return code;
}
#endif

#ifdef WIN_EX
/*
 * Quote the path to make it safe for shell command processing.
 *  We always quote it not only includes spaces in it.
 *  At least we should quote paths which include "&".
 */
char *quote_pathname(char *pathname)
{
    char *result = NULL;

    HTSprintf0(&result, "\"%s\"", pathname);
    return result;
}
#endif

const char *LYSysShell(void)
{
    const char *shell = 0;

#ifdef DOSPATH
#ifdef WIN_EX
    shell = LYGetEnv("SHELL");
    if (shell) {
	if (access(shell, 0) != 0)
	    shell = LYGetEnv("COMSPEC");
    } else {
	shell = LYGetEnv("COMSPEC");
    }
    if (shell == NULL) {
	if (system_is_NT)
	    shell = "cmd.exe";
	else
	    shell = "command.com";
    }
#else
    shell = LYGetEnv("SHELL");
    if (shell == NULL) {
	shell = LYGetEnv("COMSPEC");
    }
    if (shell == NULL) {
	shell = "command.com";
    }
#endif /* WIN_EX */
#else
#ifdef __EMX__
    if (LYGetEnv("SHELL") != NULL) {
	shell = LYGetEnv("SHELL");
    } else {
	shell = (LYGetEnv("COMSPEC") == NULL) ? "cmd.exe" : LYGetEnv("COMSPEC");
    }
#else
#ifdef VMS
    shell = "";
#else
    shell = "exec $SHELL";
#endif /* __EMX__ */
#endif /* VMS */
#endif /* DOSPATH */
    return shell;
}

#ifdef VMS
#define DISPLAY "DECW$DISPLAY"
#else
#define DISPLAY "DISPLAY"
#endif /* VMS */

/*
 * Return the X-Window $DISPLAY string if it is nonnull/nonempty
 */
char *LYgetXDisplay(void)
{
    return LYGetEnv(DISPLAY);
}

/*
 * Set the value of the X-Window $DISPLAY variable (yes it leaks memory, but
 * that is putenv's fault).
 */
void LYsetXDisplay(char *new_display)
{
    if (new_display != 0) {
#ifdef VMS
	LYUpperCase(new_display);
	Define_VMSLogical(DISPLAY, new_display);
#else
	static char *display_putenv_command;

	display_putenv_command = NULL;	/* yes, this is a leak - cannot fix */
	HTSprintf0(&display_putenv_command, "DISPLAY=%s", new_display);
	putenv(display_putenv_command);
#endif /* VMS */
	if ((new_display = LYgetXDisplay()) != 0) {
	    StrAllocCopy(x_display, new_display);
	}
    }
}

#ifdef CAN_CUT_AND_PASTE
#ifdef __EMX__

static int proc_type = -1;
static PPIB pib;
static HAB hab;
static HMQ hmq;

static void morph_PM(void)
{
    PTIB tib;
    int first = 0;

    if (proc_type == -1) {
	DosGetInfoBlocks(&tib, &pib);
	proc_type = pib->pib_ultype;
	first = 1;
    }
    if (pib->pib_ultype != 3)	/* 2 is VIO */
	pib->pib_ultype = 3;	/* 3 is PM */
    if (first)
	hab = WinInitialize(0);
    /* 64 messages if before OS/2 3.0, ignored otherwise */
    hmq = WinCreateMsgQueue(hab, 64);
    WinCancelShutdown(hmq, 1);	/* Do not inform us on shutdown */
}

static void unmorph_PM(void)
{
    WinDestroyMsgQueue(hmq);
    pib->pib_ultype = proc_type;
}

int size_clip(void)
{
    return 8192;
}

/* Code partially stolen from FED editor. */

int put_clip(const char *s)
{
    int sz = strlen(s) + 1;
    int ret = EOF, nl = 0;
    char *pByte = 0, *s1 = s, c, *t;

    while ((c = *s1++)) {
	if (c == '\r' && *s1 == '\n')
	    s1++;
	else if (c == '\n')
	    nl++;
    }
    if (DosAllocSharedMem((PPVOID) & pByte, 0, sz + nl,
			  PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE | OBJ_GETTABLE))
	return ret;

    if (!nl)
	memcpy(pByte, s, sz);
    else {
	t = pByte;
	while ((c = *t++ = *s++))
	    if (c == '\n' && (t == pByte + 1 || t[-2] != '\r'))
		t[-1] = '\r', *t++ = '\n';
    }

    morph_PM();
    if (!hab)
	goto fail;

    WinOpenClipbrd(hab);
    WinEmptyClipbrd(hab);
    if (WinSetClipbrdData(hab, (ULONG) pByte, CF_TEXT, CFI_POINTER))
	ret = 0;
    WinCloseClipbrd(hab);
    unmorph_PM();
    if (ret == 0)
	return 0;
  fail:
    DosFreeMem((PPVOID) & pByte);
    return EOF;
}

static int clip_open;

/* get_clip_grab() returns a pointer to the string in the system area.
   get_clip_release() should be called ASAP after this. */

char *get_clip_grab(void)
{
    char *ClipData;
    ULONG ulFormat;
    int sz;

    morph_PM();
    if (!hab)
	return 0;
    if (clip_open)
	get_clip_release();

    WinQueryClipbrdFmtInfo(hab, CF_TEXT, &ulFormat);
    if (ulFormat != CFI_POINTER) {
	unmorph_PM();
	return 0;
    }
    WinOpenClipbrd(hab);
    clip_open = 1;
    ClipData = (char *) WinQueryClipbrdData(hab, CF_TEXT);
    sz = strlen(ClipData);
    if (!ClipData || !sz) {
	get_clip_release();
	return 0;
    }
    return ClipData;
}

void get_clip_release(void)
{
    if (!clip_open)
	return;
    WinCloseClipbrd(hab);
    clip_open = 0;
    unmorph_PM();
}

#elif defined(WIN_EX) /* 1997/10/16 (Thu) 20:13:28 */

int put_clip(const char *szBuffer)
{
    HANDLE hWnd;
    HANDLE m_hLogData;
    LPTSTR pLogData;
    HANDLE hClip;
    int len;

    if (szBuffer == NULL)
	return EOF;

    len = (int) strlen(szBuffer);
    if (len == 0)
	return EOF;
    else
	len++;

    m_hLogData = GlobalAlloc(GHND, len);
    if (m_hLogData == NULL) {
	return EOF;
    }

    hWnd = NULL;
    if (!OpenClipboard(hWnd)) {
	return EOF;
    }
    /* Remove the current Clipboard contents */
    if (!EmptyClipboard()) {
	GlobalFree(m_hLogData);
	return EOF;
    }

    /* Lock the global memory while we write to it. */
    pLogData = (LPTSTR) GlobalLock(m_hLogData);

    lstrcpy((LPTSTR) pLogData, szBuffer);
    GlobalUnlock(m_hLogData);

    /* If there were any lines at all then copy them to clipboard. */
    hClip = SetClipboardData(CF_TEXT, m_hLogData);
    if (!hClip) {
	/* If we couldn't clip the data then free the global handle. */
	GlobalFree(m_hLogData);
    }

    CloseClipboard();
    return 0;
}

static HANDLE m_hLogData;
static int m_locked;

/* get_clip_grab() returns a pointer to the string in the system area.
   get_clip_release() should be called ASAP after this. */

char *get_clip_grab()
{
    HANDLE hWnd;
    LPTSTR pLogData;

    hWnd = NULL;
    if (!OpenClipboard(hWnd)) {
	return 0;
    }

    m_hLogData = GetClipboardData(CF_TEXT);

    if (m_hLogData == NULL) {
	CloseClipboard();
	m_locked = 0;
	return 0;
    }
    pLogData = (LPTSTR) GlobalLock(m_hLogData);

    m_locked = 1;
    return pLogData;
}

void get_clip_release()
{
    if (!m_locked)
	return;
    GlobalUnlock(m_hLogData);
    CloseClipboard();
    m_locked = 0;
}

#elif defined(HAVE_POPEN)

static FILE *paste_handle = 0;
static char *paste_buf = NULL;

void get_clip_release(void)
{
    if (paste_handle != 0)
	pclose(paste_handle);
    if (paste_buf)
	FREE(paste_buf);
}

static int clip_grab(void)
{
    char *cmd = LYGetEnv("RL_PASTE_CMD");

    if (paste_handle)
	pclose(paste_handle);
    if (!cmd)
	return 0;

    paste_handle = popen(cmd, TXT_R);
    if (!paste_handle)
	return 0;
    return 1;
}

#define PASTE_BUFFER 1008
#define CF_TEXT 0		/* Not used */

char *get_clip_grab(void)
{
    int len;
    unsigned size = PASTE_BUFFER;
    int off = 0;

    if (!clip_grab())
	return NULL;
    if (!paste_handle)
	return NULL;
    if (paste_buf)
	FREE(paste_buf);
    paste_buf = typeMallocn(char, PASTE_BUFFER);

    while (1) {
	len = (int) fread(paste_buf + off,
			  (size_t) 1,
			  (size_t) PASTE_BUFFER - 1,
			  paste_handle);
	paste_buf[off + len] = '\0';
	if (len < PASTE_BUFFER - 1)
	    break;
	if (StrChr(paste_buf + off, '\r')
	    || StrChr(paste_buf + off, '\n'))
	    break;
	paste_buf = typeRealloc(char, paste_buf, size += PASTE_BUFFER - 1);

	off += len;
    }
    return paste_buf;
}

int put_clip(const char *s)
{
    char *cmd = LYGetEnv("RL_CLCOPY_CMD");
    FILE *fh;
    size_t l = strlen(s), res;

    if (!cmd)
	return -1;

    fh = popen(cmd, TXT_W);
    if (!fh)
	return -1;
    res = fwrite(s, (size_t) 1, l, fh);
    if (pclose(fh) != 0 || res != l)
	return -1;
    return 0;
}

#endif /* __EMX__ ... HAVE_POPEN */
#endif /* CAN_CUT_AND_PASTE */

/*
 * Sleep for a number of milli-sec.
 */
void LYmsec_delay(unsigned msec)
{
#if defined(_WINDOWS)
    Sleep(msec);

#elif defined(HAVE_NAPMS)
    napms((int) msec);

#elif defined(DJGPP) || defined(HAVE_USLEEP)
    usleep(1000 * msec);

#else
    struct timeval tv;
    unsigned long usec = 1000UL * msec;

    tv.tv_sec = usec / 1000000UL;
    tv.tv_usec = usec % 1000000UL;
    select(0, NULL, NULL, NULL, &tv);
#endif
}

#if defined(WIN_EX)

#ifndef WSABASEERR
#define WSABASEERR 10000
#endif

#ifdef ENABLE_IPV6
#define WSOCK_NAME  "ws2_32"
#else
#define WSOCK_NAME  "wsock32"
#endif

/*
 * Description: the windows32 version of perror()
 *
 * Returns:  a pointer to a static error
 *
 * Notes/Dependencies:  I got this from
 *	comp.os.ms-windows.programmer.win32
 */
char *w32_strerror(DWORD ercode)
{
/*  __declspec(thread) necessary if you will use multiple threads */
#if defined(__CYGWIN__) || defined(__MINGW32__)
    static char msg_buff[256];

#else
    __declspec(thread) static char msg_buff[256];
#endif
    HMODULE hModule;
    int i, msg_type;
    unsigned char *p, *q, tmp_buff[256];
    DWORD rc;

    hModule = NULL;
    msg_type = FORMAT_MESSAGE_FROM_SYSTEM;
    /* Fill message buffer with a default message in
     * case FormatMessage fails
     */
    wsprintf(msg_buff, "Error %ld", ercode);

    /*
     * Special code for winsock error handling.
     */
    if (ercode > WSABASEERR) {
	hModule = GetModuleHandle(WSOCK_NAME);
	if (hModule)
	    msg_type = FORMAT_MESSAGE_FROM_HMODULE;
    }
    /*
     * message handling. If not found in module, retry from system.
     */
    rc = FormatMessage(msg_type, hModule, ercode, LANG_NEUTRAL,
		       msg_buff, sizeof(msg_buff), NULL);

    if (rc == 0 && msg_type == FORMAT_MESSAGE_FROM_HMODULE) {
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ercode,
		      LANG_NEUTRAL, msg_buff, sizeof(msg_buff), NULL);
    }

    strcpy((char *) tmp_buff, msg_buff);
    p = q = tmp_buff;
    i = 0;
    while (*p) {
	if (!(*p == '\n' || *p == '\r'))
	    msg_buff[i++] = *p;
	p++;
    }
    msg_buff[i] = '\0';

    return msg_buff;
}

#endif

#if defined(SYSLOG_REQUESTED_URLS)
/*
 * syslog() interface
 */
void LYOpenlog(const char *banner)
{
    if (syslog_requested_urls) {
	CTRACE((tfp, "LYOpenlog(%s)\n", NONNULL(banner)));
#if defined(DJGPP)
	openlog("lynx", LOG_PID | LOG_NDELAY, LOG_LOCAL5);
#else
	openlog("lynx", LOG_PID, LOG_LOCAL5);
#endif

	if (banner) {
	    syslog(LOG_INFO, "Session start:%s", banner);
	} else {
	    syslog(LOG_INFO, "Session start");
	}
    }
}

static BOOLEAN looks_like_password(char *first,
				   char *last)
{
    BOOLEAN result = FALSE;

    while (first <= last) {
	if (*first == '/'
	    || *first == ':') {
	    result = FALSE;
	    break;
	}
	result = TRUE;
	first++;
    }
    return result;
}

void LYSyslog(char *arg)
{
    char *colon1;
    char *colon2;
    char *atsign;

    if (syslog_requested_urls) {

	CTRACE((tfp, "LYSyslog %s\n", arg));

	if (is_url(arg)) {	/* proto://user:password@host/path:port */
	    /*      ^this colon                 */
	    if ((colon1 = StrChr(arg, ':')) != 0
		&& !StrNCmp(colon1, "://", 3)
		&& (colon2 = StrChr(colon1 + 3, ':')) != 0
		&& (atsign = StrChr(colon1, '@')) != 0
		&& (colon2 < atsign)
		&& looks_like_password(colon2 + 1, atsign - 1)) {
		char *buf = NULL;

		StrAllocCopy(buf, arg);
		buf[colon2 - arg + 1] = 0;
		StrAllocCat(buf, "******");
		StrAllocCat(buf, atsign);
		syslog(LOG_INFO | LOG_LOCAL5, "%s", buf);
		CTRACE((tfp, "...alter %s\n", buf));
		FREE(buf);
		return;
	    }
	}
	syslog(LOG_INFO | LOG_LOCAL5, "%s", NONNULL(arg));
    }
}

void LYCloselog(void)
{
    if (syslog_requested_urls) {
	syslog(LOG_INFO, "Session over");
	closelog();
    }
}

#endif /* SYSLOG_REQUESTED_URLS */

#if defined(WIN_EX) || defined(__CYGWIN__)	/* 2000/03/07 (Tue) 17:17:46 */

#define IS_SEP(p)	((p == '\\') || (p == '/') || (p == ':'))

static char *black_list[] =
{
    "con",
    "prn",
    "clock$",
    "config$",
    NULL
};

static int is_device(char *fname)
{
    HANDLE fileHandle;
    DWORD val;
    int i;

    i = 0;
    while (black_list[i] != NULL) {
	if (strcasecomp(fname, black_list[i]) == 0) {
	    return 1;		/* device file */
	}
	i++;
    }

    fileHandle = CreateFile(fname, 0, 0, 0, OPEN_EXISTING, 0, 0);

    if (fileHandle == INVALID_HANDLE_VALUE) {
	return 0;		/* normal file */
    } else {
	val = GetFileType(fileHandle);
	switch (val) {
	case 1:
	    val = 0;
	    break;
	case 2:
	    val = 1;		/* device file */
	    break;
	default:
	    val = 0;
	    break;
	}

	CloseHandle(fileHandle);
    }
    return val;
}

static char *device_list[] =
{
    "con",
    "nul",
    "aux",
    "prn",
    NULL
};

int unsafe_filename(const char *fname)
{
    int i, len, sum;
    char *cp;
    char *save;

    i = 0;
    while (device_list[i] != NULL) {
	if (strcasecomp(fname, device_list[i]) == 0) {
	    return 0;		/* device file (open OK) */
	}
	i++;
    }

    save = cp = strdup(fname);

    while (*cp) {
	if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp)))
	    cp += 2;		/* KANJI skip */
	if (IS_SEP(*cp)) {
	    *cp = '\0';
	}
	cp++;
    }

    sum = 0;
    cp = save;
    len = (int) strlen(fname);
    while (cp < (save + len)) {
	if (*cp == '\0') {
	    cp++;
	} else {
	    char *q;

	    q = StrChr(cp, '.');
	    if (q)
		*q = '\0';
	    if (is_device(cp)) {
		sum++;
		break;
	    }
	    if (q)
		cp = q + 1;
	    while (*cp)
		cp++;
	}
    }
    free(save);

    return (sum != 0);
}

FILE *safe_fopen(const char *fname, const char *mode)
{
    if (unsafe_filename(fname)) {
	return (FILE *) NULL;
    } else {
	return fopen(fname, mode);
    }
}

#endif /* defined(WIN_EX) || defined(__CYGWIN__) */
