/*
* $LynxId: LYUtils.c,v 1.292 2018/05/15 21:20: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 adress.
*/
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) &&
STREQ(LYLocalDomain,
me.ut_host + ulen - strlen(LYLocalDomain))) {
result = TRUE;
}
#ifdef LINUX
/* Linux fix to check for local user. J.Cullen 11Jul94 */
else if (strlen(me.ut_host) == 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 size_change(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)
/*
* Called from start_curses().
*/
return;
#else /* Curses: */
#ifdef HAVE_SIZECHANGE
#ifdef TIOCGSIZE
struct ttysize win;
#else
#ifdef TIOCGWINSZ
struct winsize win;
#endif /* TIOCGWINSZ */
#endif /* TIOCGSIZE */
#ifdef TIOCGSIZE
if (ioctl(0, TIOCGSIZE, &win) == 0) {
if (win.ts_lines != 0) {
LYlines = win.ts_lines;
}
if (win.ts_cols != 0) {
LYcols = win.ts_cols;
}
}
#else
#ifdef TIOCGWINSZ
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;
}
}
#endif /* TIOCGWINSZ */
#endif /* TIOCGSIZE */
#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
}
#ifdef SIGWINCH
LYExtSignal(SIGWINCH, size_change);
#endif /* SIGWINCH */
return;
}
/*
* 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 = lynx_rand();
long max_rand = 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);
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, <able);
return;
}
lvalue.dsc$w_length = strlen(LogicalValue);
lvalue.dsc$a_pointer = LogicalValue;
lib$set_logical(&lname, &lvalue, <able, 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", 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);
/*
* 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, (int) 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 MY_DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
#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(MY_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, MY_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();
}
#else /* !( defined __EMX__ ) */
# if !defined(WIN_EX) && 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 /* !defined(WIN_EX) && defined(HAVE_POPEN) */
#endif /* __EMX__ */
/*
* 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) /* 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;
}
#endif /* WIN_EX */
#endif /* CAN_CUT_AND_PASTE */
#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__) */