diff options
Diffstat (limited to 'src/LYUtils.c')
-rw-r--r-- | src/LYUtils.c | 8038 |
1 files changed, 8038 insertions, 0 deletions
diff --git a/src/LYUtils.c b/src/LYUtils.c new file mode 100644 index 0000000..e45e641 --- /dev/null +++ b/src/LYUtils.c @@ -0,0 +1,8038 @@ +/* + * $LynxId: LYUtils.c,v 1.309 2024/01/15 17:10:52 tom Exp $ + */ +#include <HTUtils.h> +#include <HTTCP.h> +#include <HTParse.h> +#include <HTAccess.h> +#include <HTCJK.h> +#include <HTAlert.h> + +#if defined(__MINGW32__) + +extern int kbhit(void); /* FIXME: use conio.h */ + +#undef UNIX + +#elif defined(_WINDOWS) + +#ifdef DONT_USE_GETTEXT +#undef gettext +#elif defined(HAVE_GETTEXT) +#undef gettext +#define gettext conio_gettext +#else +#undef gettext +#endif + +#include <conio.h> + +#ifdef DONT_USE_GETTEXT +#define gettext(s) s +#elif defined(HAVE_GETTEXT) +#undef gettext +#ifdef _INTL_REDIRECT_MACROS +#define gettext libintl_gettext /* restore definition from libintl.h */ +#endif +#else +#undef gettext +#define gettext(s) s +#endif + +#if !defined(kbhit) && defined(_WCONIO_DEFINED) +#define kbhit() _kbhit() /* reasonably recent conio.h */ +#endif +#elif defined(__minix) +#include <termios.h> /* for struct winsize */ + +#endif /* __MINGW32__ */ + +#include <LYCurses.h> +#include <LYHistory.h> +#include <LYStrings.h> +#include <LYGlobalDefs.h> +#include <LYUtils.h> +#include <LYSignal.h> +#include <GridText.h> +#include <LYClean.h> +#include <LYCharSets.h> +#include <LYCharUtils.h> + +#include <LYMainLoop.h> +#include <LYKeymap.h> + +#ifdef __DJGPP__ +#include <go32.h> +#include <sys/exceptn.h> +#endif /* __DJGPP__ */ + +#ifndef NO_GROUPS +#include <HTFile.h> +#endif + +#ifdef _WINDOWS /* 1998/04/30 (Thu) 19:04:25 */ +#define GETPID() (unsigned) (getpid() & 0xffff) +#else +#define GETPID() (unsigned) getpid() +#endif /* _WINDOWS */ + +#ifdef FNAMES_8_3 +#define PID_FMT "%04x" +#else +#define PID_FMT "%u" +#endif + +#ifdef DJGPP_KEYHANDLER +#include <bios.h> +#endif /* DJGPP_KEYHANDLER */ + +#ifdef __EMX__ +# define BOOLEAN OS2_BOOLEAN /* Conflicts, but is used */ +# undef HT_ERROR /* Conflicts too */ +# define INCL_PM /* I want some PM functions.. */ +# define INCL_DOSPROCESS /* TIB PIB. */ +# include <os2.h> +# undef BOOLEAN +#endif + +#ifdef VMS +#include <descrip.h> +#include <libclidef.h> +#include <lib$routines.h> +#endif /* VMS */ + +#ifdef HAVE_UTMP +#include <pwd.h> +#ifdef UTMPX_FOR_UTMP +#include <utmpx.h> +#define utmp utmpx +#ifdef UTMPX_FILE +#ifdef UTMP_FILE +#undef UTMP_FILE +#endif /* UTMP_FILE */ +#define UTMP_FILE UTMPX_FILE +#else +#ifdef __UTMPX_FILE +#define UTMP_FILE __UTMPX_FILE /* at least in OS/390 S/390 -- gil -- 2100 */ +#else +#ifndef UTMP_FILE +#define UTMP_FILE "/var/adm/utmpx" /* Digital Unix 4.0 */ +#endif +#endif +#endif /* UTMPX_FILE */ +#else +#include <utmp.h> +#endif /* UTMPX_FOR_UTMP */ +#endif /* HAVE_UTMP */ + +#ifdef NEED_PTEM_H +/* they neglected to define struct winsize in termios.h -- it's only in + * termio.h and ptem.h (the former conflicts with other definitions). + */ +#include <sys/stream.h> +#include <sys/ptem.h> +#endif + +#include <LYLeaks.h> + +#ifdef USE_COLOR_STYLE +#include <AttrList.h> +#include <LYHash.h> +#include <LYStyle.h> +#endif + +#ifdef SVR4_BSDSELECT +extern int BSDselect(int nfds, fd_set * readfds, fd_set * writefds, + fd_set * exceptfds, struct timeval *timeout); + +#ifdef select +#undef select +#endif /* select */ +#define select BSDselect +#ifdef SOCKS +#ifdef Rselect +#undef Rselect +#endif /* Rselect */ +#define Rselect BSDselect +#endif /* SOCKS */ +#endif /* SVR4_BSDSELECT */ + +#ifdef __DJGPP__ +#undef select /* defined to select_s in www_tcp.h */ +#endif + +#ifndef UTMP_FILE +#if defined(__FreeBSD__) || defined(__bsdi__) +#define UTMP_FILE _PATH_UTMP +#else +#define UTMP_FILE "/etc/utmp" +#endif /* __FreeBSD__ || __bsdi__ */ +#endif /* !UTMP_FILE */ + +/* + * experimental - make temporary filenames random to make the scheme less + * obvious. However, as noted by KW, there are instances (such as the + * 'O'ption page, for which Lynx will store a temporary filename even when + * it no longer applies, since it will reuse that filename at a later time. + */ +#ifdef USE_RAND_TEMPNAME +#if defined(LYNX_RAND_MAX) +#define HAVE_RAND_TEMPNAME 1 +#define MAX_TEMPNAME 10000 +#ifndef BITS_PER_CHAR +#define BITS_PER_CHAR 8 +#endif +#endif +#endif + +#define COPY_COMMAND "%s %s %s" + +static HTList *localhost_aliases = NULL; /* Hosts to treat as local */ +static char *HomeDir = NULL; /* HOME directory */ + +HTList *sug_filenames = NULL; /* Suggested filenames */ + +/* + * Maintain a list of all of the temp-files we create so that we can remove + * them during the cleanup. + */ +typedef struct _LYTemp { + struct _LYTemp *next; + char *name; + BOOLEAN outs; + FILE *file; +} LY_TEMP; + +static LY_TEMP *ly_temp; + +static LY_TEMP *FindTempfileByName(const char *name) +{ + LY_TEMP *p; + + for (p = ly_temp; p != 0; p = p->next) { + if (!strcmp(p->name, name)) { + break; + } + } + return p; +} + +static LY_TEMP *FindTempfileByFP(FILE *fp) +{ + LY_TEMP *p; + + for (p = ly_temp; p != 0; p = p->next) { + if (p->file == fp) { + break; + } + } + return p; +} + +#if defined(_WIN32) +/* + * Use RegQueryValueExA() rather than RegQueryValueEx() for compatibility + * with non-Unicode winvile + */ +static int w32_get_reg_sz(HKEY hkey, const char *name, char *value, unsigned length) +{ + int result; + DWORD dwSzBuffer = length; + + CTRACE((tfp, "w32_get_reg_sz(%s)\n", name)); + result = RegQueryValueExA(hkey, + name, + NULL, + NULL, + (LPBYTE) value, + &dwSzBuffer); + if (result == ERROR_SUCCESS) { + value[dwSzBuffer] = 0; + CTRACE((tfp, "->%s\n", value)); + } + return result; +} + +static char *w32_get_shell_folder(const char *name) +{ + static HKEY rootkey = HKEY_CURRENT_USER; + + char *result = 0; + HKEY hkey; + char buffer[LY_MAXPATH]; + + if (RegOpenKeyEx(rootkey, + W32_STRING("Software" + "\\Microsoft" + "\\Windows" + "\\CurrentVersion" + "\\Explorer" + "\\Shell Folders"), + 0, + KEY_READ, + &hkey) == ERROR_SUCCESS) { + if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) { + + result = strdup(buffer); + (void) RegCloseKey(hkey); + } + + (void) RegCloseKey(hkey); + } + return non_empty(result) ? result : 0; +} +#endif + +/* + * Get an environment variable, rejecting empty strings + */ +char *LYGetEnv(const char *name) +{ + char *result = getenv(name); + +#if defined(_WIN32) + if (result == 0) { + static HKEY rootkeys[] = + {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + + int j; + HKEY hkey; + char buffer[256]; + + for (j = 0; j < (int) TABLESIZE(rootkeys); ++j) { + if (RegOpenKeyEx(rootkeys[j], + LYNX_SUBKEY W32_STRING("\\Environment"), + 0, + KEY_READ, + &hkey) == ERROR_SUCCESS) { + if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) { + + result = strdup(buffer); + (void) RegCloseKey(hkey); + break; + } + + (void) RegCloseKey(hkey); + } + } + } +#endif + return non_empty(result) ? result : 0; +} + +/* + * ascii versions of locale sensitive functions needed because in + * Turkish locales tolower("I") is not "i". That's fatal for case + * sensitive operations with charset names, HTML tags etc. + */ +#ifdef USE_ASCII_CTYPES +int ascii_tolower(int i) +{ + if (91 > i && i > 64) + return (i + 32); + else + return i; +} + +int ascii_toupper(int i) +{ + if (123 > i && i > 96) + return (i - 32); + else + return i; +} + +int ascii_isupper(int i) +{ + if (91 > i && i > 64) + return 1; + else + return 0; +} +#endif /* USE_ASCII_CTYPES */ + +/* + * Check for UTF-8 data, returning the length past the first character. + * Return zero if we found an ordinary character rather than UTF-8. + */ +size_t utf8_length(int utf_flag, + const char *data) +{ + size_t utf_extra = 0; + + if (utf_flag && is8bits(*data)) { + if ((*data & 0xe0) == 0xc0) { + utf_extra = 1; + } else if ((*data & 0xf0) == 0xe0) { + utf_extra = 2; + } else if ((*data & 0xf8) == 0xf0) { + utf_extra = 3; + } else if ((*data & 0xfc) == 0xf8) { + utf_extra = 4; + } else if ((*data & 0xfe) == 0xfc) { + utf_extra = 5; + } else { + /* + * Garbage. + */ + utf_extra = 0; + } + if (strlen(data + 1) < utf_extra) { + /* + * Shouldn't happen. + */ + utf_extra = 0; + } + } + return utf_extra; +} + +/* + * Free storage used for the link-highlighting. + */ +void LYFreeHilites(int first, int last) +{ + int i; + + for (i = first; i < last; i++) { + LYSetHilite(i, NULL); + FREE(links[i].lname); + } +} + +#define LXP (links[cur].lx) +#define LYP (links[cur].ly) + +/* + * Set the initial highlight information for a given link. + */ +void LYSetHilite(int cur, + const char *text) +{ + links[cur].list.hl_base.hl_text = DeConst(text); + links[cur].list.hl_len = (short) ((text != NULL) ? 1 : 0); + FREE(links[cur].list.hl_info); +} + +/* + * Add highlight information for the next line of a link. + */ +void LYAddHilite(int cur, + char *text, + int x) +{ + HiliteList *list = &(links[cur].list); + HiliteInfo *have = list->hl_info; + size_t need = (unsigned) (list->hl_len - 1); + size_t want; + + list->hl_len = (short) (list->hl_len + 1); + want = (size_t) list->hl_len; + + if (have != NULL) { + have = typeRealloc(HiliteInfo, have, want); + } else { + have = typeMallocn(HiliteInfo, want); + } + list->hl_info = have; + have[need].hl_text = text; + have[need].hl_x = (short) x; +} + +/* + * Get the highlight text, counting from zero. + */ +const char *LYGetHiliteStr(int cur, + int count) +{ + const char *result; + + if (count >= links[cur].list.hl_len) + result = NULL; + else if (count > 0) + result = links[cur].list.hl_info[count - 1].hl_text; + else + result = links[cur].list.hl_base.hl_text; + return result; +} + +/* + * Get the X-ordinate at which to draw the corresponding highlight-text + */ +int LYGetHilitePos(int cur, + int count) +{ + int result; + + if (count >= links[cur].list.hl_len) + result = -1; + else if (count > 0) + result = links[cur].list.hl_info[count - 1].hl_x; + else + result = LXP; + return result; +} + +#ifdef SHOW_WHEREIS_TARGETS + +#define SKIP_GLYPHS(theFlag, theData, theOffset) \ + (theFlag \ + ? LYmbcs_skip_glyphs(theData, (theOffset), theFlag) \ + : (theData + (theOffset))) + +/* + * If we have an emphasized WHEREIS hit in the highlighted text, restore the + * emphasis. Note that we never emphasize the first and last characters of the + * highlighted text when we are making the link current, so the link attributes + * for the current link will persist at the beginning and end, providing an + * indication to the user that it has been made current. Also note that we use + * HText_getFirstTargetInLine() to determine if there's a hit in the HText + * structure line containing the link, and if so, get back a copy of the line + * starting at that first hit (which might be before or after our link), and + * with all IsSpecial characters stripped, so we don't need to deal with them + * here. -FM + */ +static BOOL show_whereis_targets(int flag, + int cur, + int count, + const char *target, + int TargetEmphasisON, + int utf_flag) +{ + const char *mydata = NULL; + const char *cp; + char *theData = NULL; + char buffer[MAX_LINE]; + char tmp[7]; + int HitOffset; + int LenNeeded; + int Offset; + int tLen; + + tmp[0] = tmp[1] = tmp[2] = '\0'; + + if (non_empty(target) + && (links[cur].type & WWW_LINK_TYPE) + && non_empty(LYGetHiliteStr(cur, count)) + && LYP + count < display_lines + && HText_getFirstTargetInLine(HTMainText, + links[cur].anchor_line_num + count, + utf_flag, + &Offset, + &tLen, + &theData, + target)) { + int itmp, written, len, y, offset; + const char *data; + int tlen = (int) strlen(target); + int hlen, hLen; + int hLine = LYP + count; + int hoffset = LYGetHilitePos(cur, count); + size_t utf_extra = 0; + + /* + * Copy into the buffer only what will fit up to the right border of + * the screen. -FM + */ + LYmbcsstrncpy(buffer, + NonNull(LYGetHiliteStr(cur, count)), + (int) (sizeof(buffer) - 1), + (LYcolLimit - LYGetHilitePos(cur, count)), + utf_flag); + hlen = (int) strlen(buffer); + hLen = ((IS_CJK_TTY || utf_flag) ? + LYmbcsstrlen(buffer, utf_flag, YES) : hlen); + + /* + * Break out if the first hit in the line starts after this link. -FM + */ + if (Offset < (hoffset + hLen)) { + /* + * Recursively skip hits that end before this link, and break out + * if there is no hit beyond those. -FM + */ + mydata = theData; + while ((Offset < hoffset) && + ((Offset + tLen) <= hoffset)) { + data = (mydata + tlen); + offset = (Offset + tLen); + if (((cp = LYno_attr_mb_strstr(data, + target, + utf_flag, YES, + &HitOffset, + &LenNeeded)) != NULL) + && (offset + LenNeeded) < LYcols) { + mydata = cp; + Offset = (offset + HitOffset); + } else { + goto highlight_search_done; + } + } + data = buffer; + offset = hoffset; + + /* + * If the hit starts before the hightext, and ends in or beyond the + * hightext, restore the emphasis, skipping the first and last + * characters of the hightext if we're making the link current. + * -FM + */ + if (offset >= 0 && + (Offset < offset) && + ((Offset + tLen) > offset)) { + itmp = 0; + written = 0; + len = (tlen - (offset - Offset)); + + /* + * Go to the start of the hightext and handle its first + * character. -FM + */ + LYmove(hLine, offset); + tmp[0] = data[itmp]; + utf_extra = utf8_length(utf_flag, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra); + itmp += (int) utf_extra; + /* + * Start emphasis immediately if we are making the link + * non-current. -FM + */ + if (flag != TRUE) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 1)); + } + tmp[1] = '\0'; + written += (int) (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + /* + * Start emphasis immediately if we are making the link + * non-current. -FM + */ + if (flag != TRUE) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 1)); + } + tmp[1] = '\0'; + written += 2; + } else { + /* + * Start emphasis immediately if we are making the link + * non-current. -FM + */ + if (flag != TRUE) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 1)); + } + written++; + } + itmp++; + /* + * Start emphasis after the first character if we are making + * the link current and this is not the last character. -FM + */ + if (!TargetEmphasisON && + data[itmp] != '\0') { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + } + + /* + * Handle the remaining characters. -FM + */ + for (; + written < len && (tmp[0] = data[itmp]) != '\0'; + itmp++) { + /* + * Print all the other target chars, except the last + * character if it is also the last character of hightext + * and we are making the link current. -FM + */ + utf_extra = utf8_length(utf_flag, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra); + itmp += (int) utf_extra; + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + (void) y; + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += (int) (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += 2; + } else { + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + written++; + } + } + + /* + * Stop the emphasis if we haven't already, then reset the + * offset to our current position in the line, and if that is + * beyond the link, or or we are making the link current and it + * is the last character of the hightext, we are done. -FM + */ + if (TargetEmphasisON) { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + } + LYGetYX(y, offset); + if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen)) + /* + * See if we have another hit that starts within the + * hightext. -FM + */ + && ((cp = + LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag, + mydata, + offset - Offset), + target, + utf_flag, YES, + &HitOffset, + &LenNeeded)) != NULL) + && (offset + LenNeeded) < LYcols + /* + * If the hit starts after the end of the hightext, or we + * are making the link current and the hit starts at its + * last character, we are done. -FM + */ + && (HitOffset + offset) < + (hoffset + + (flag == TRUE ? (hLen - 1) : hLen))) { + /* + * Set up the data and offset for the hit, and let the code + * for within hightext hits handle it. -FM + */ + mydata = cp; + Offset = (offset + HitOffset); + data = buffer; + offset = hoffset; + goto highlight_hit_within_hightext; + } + goto highlight_search_done; + } + + highlight_hit_within_hightext: + /* + * If we get to here, the hit starts within the hightext. If we + * are making the link current and it's the last character in the + * hightext, we are done. Otherwise, move there and start + * restoring the emphasis. -FM + */ + if ((Offset - offset) <= (flag == TRUE ? (hLen - 1) : hLen)) { + data = SKIP_GLYPHS(utf_flag, data, Offset - offset); + if (utf_flag) { + LYrefresh(); + } + offset = Offset; + itmp = 0; + written = 0; + len = tlen; + + /* + * Go to the start of the hit and handle its first character. + * -FM + */ + LYmove(hLine, offset); + tmp[0] = data[itmp]; + utf_extra = utf8_length(utf_flag, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra); + itmp += (int) utf_extra; + /* + * Start emphasis immediately if we are making the link + * non-current, or we are making it current but this is not + * the first or last character of the hightext. -FM + */ + if (flag != TRUE || + (offset > hoffset && data[itmp + 1] != '\0')) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 1)); + } + tmp[1] = '\0'; + written += (int) (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + /* + * Start emphasis immediately if we are making the link + * non-current, or we are making it current but this is not + * the first or last character of the hightext. -FM + */ + if (flag != TRUE || + (offset > hoffset && data[itmp + 1] != '\0')) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 2)); + } + tmp[1] = '\0'; + written += 2; + } else { + /* + * Start emphasis immediately if we are making the link + * non-current, or we are making it current but this is not + * the first or last character of the hightext. -FM + */ + if (flag != TRUE || + (offset > hoffset && data[itmp + 1] != '\0')) { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + LYaddstr(tmp); + } else { + LYmove(hLine, (offset + 1)); + } + written++; + } + itmp++; + /* + * Start emphasis after the first character if we are making + * the link current and this is not the last character. -FM + */ + if (!TargetEmphasisON && + data[itmp] != '\0') { + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + } + + for (; + written < len && (tmp[0] = data[itmp]) != '\0'; + itmp++) { + /* + * Print all the other target chars, except the last + * character if it is also the last character of hightext + * and we are making the link current. -FM + */ + utf_extra = utf8_length(utf_flag, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra); + itmp += (int) utf_extra; + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += (int) (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += 2; + } else { + /* + * Make sure we don't restore emphasis to the last + * character of hightext if we are making the link + * current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + written++; + } + } + + /* + * Stop the emphasis if we haven't already, then reset the + * offset to our current position in the line, and if that is + * beyond the link, or we are making the link current and it is + * the last character in the hightext, we are done. -FM + */ + if (TargetEmphasisON) { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + } + LYGetYX(y, offset); + if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen)) + /* + * See if we have another hit that starts within the + * hightext. -FM + */ + && ((cp = + LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag, + mydata, + offset - Offset), + target, + utf_flag, YES, + &HitOffset, + &LenNeeded)) != NULL) + && (offset + LenNeeded) < LYcols + /* + * If the hit starts after the end of the hightext, or we + * are making the link current and the hit starts at its + * last character, we are done. -FM + */ + && (HitOffset + offset) < + (hoffset + (flag == TRUE ? (hLen - 1) : hLen))) { + /* + * If the target extends beyond our buffer, emphasize + * everything in the hightext starting at this hit. + * Otherwise, set up the data and offsets, and loop back. + * -FM + */ + if ((HitOffset + (offset + tLen)) >= (hoffset + hLen)) { + offset = (HitOffset + offset); + data = SKIP_GLYPHS(utf_flag, mydata, offset - hoffset); + if (utf_flag) { + LYrefresh(); + } + LYmove(hLine, offset); + itmp = 0; + written = 0; + len = (int) strlen(data); + + /* + * Turn the emphasis back on. -FM + */ + LYstartTargetEmphasis(); + TargetEmphasisON = TRUE; + for (; + written < len && (tmp[0] = data[itmp]) != '\0'; + itmp++) { + /* + * Print all the other target chars, except the + * last character if it is also the last character + * of hightext and we are making the link current. + * -FM + */ + utf_extra = utf8_length(utf_flag, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra); + itmp += (int) utf_extra; + /* + * Make sure we don't restore emphasis to the + * last character of hightext if we are making + * the link current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + LYGetYX(y, offset); + LYmove(hLine, (offset + 1)); + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += (int) (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + /* + * Make sure we don't restore emphasis to the + * last character of hightext if we are making + * the link current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + } else { + LYaddstr(tmp); + } + tmp[1] = '\0'; + written += 2; + } else { + /* + * Make sure we don't restore emphasis to the + * last character of hightext if we are making + * the link current. -FM + */ + if (flag == TRUE && data[(itmp + 1)] == '\0') { + LYstopTargetEmphasis(); + TargetEmphasisON = FALSE; + } else { + LYaddstr(tmp); + } + written++; + } + } + /* + * Turn off the emphasis if we haven't already, and + * then we're done. -FM + */ + if (TargetEmphasisON) { + LYstopTargetEmphasis(); + } + } else { + mydata = cp; + Offset = (offset + HitOffset); + data = buffer; + offset = hoffset; + goto highlight_hit_within_hightext; + } + } + } + } + } + highlight_search_done: + FREE(theData); + return (BOOLEAN) TargetEmphasisON; +} +#endif /* SHOW_WHEREIS_TARGETS */ + +#ifdef USE_COLOR_STYLE +static int find_cached_style(int cur, + int flag) +{ + int s = s_alink; + +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (textfields_need_activation + && links[cur].type == WWW_FORM_LINK_TYPE + && F_TEXTLIKE(links[cur].l_form->type)) + s = s_curedit; +#endif + + if (flag != TRUE) { + int x; + + /* + * This is where we try to restore the original style when a link is + * unhighlighted. The cached styles array saves the original style + * just for this case. If it doesn't have a color change saved at just + * the right position, we look at preceding positions in the same line + * until we find one. + */ + if (ValidCachedStyle(LYP, LXP)) { + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.highlight.off: cached style @(%d,%d): ", + LYP, LXP)); + s = (int) GetCachedStyle(LYP, LXP); + if (s == 0) { + for (x = LXP - 1; x >= 0; x--) { + s = (int) GetCachedStyle(LYP, x); + if (s != 0) { + SetCachedStyle(LYP, LXP, (unsigned) s); + CTRACE2(TRACE_STYLE, + (tfp, "found %d, x_offset=%d.\n", s, x - LXP)); + break; + } + } + if (s == 0) { + CTRACE2(TRACE_STYLE, (tfp, "not found, assume <a>.\n")); + s = s_a; + } + } else { + CTRACE2(TRACE_STYLE, (tfp, "found %d.\n", s)); + } + } else { + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.highlight.off: can't use cache.\n")); + s = s_a; + } + } else { + CTRACE2(TRACE_STYLE, (tfp, "STYLE.highlight.on: @(%d,%d).\n", LYP, LXP)); + } + return s; +} +#endif /* USE_COLOR_STYLE */ + +/* + * Highlight (or unhighlight) a given link. + */ +void LYhighlight(int flag, + int cur, + const char *target) +{ + char buffer[MAX_LINE]; + int i; + int hi_count; + int hi_offset; + int title_adjust = (no_title ? -TITLE_LINES : 0); + char tmp[7]; + const char *hi_string; + +#ifdef SHOW_WHEREIS_TARGETS + BOOL TargetEmphasisON = FALSE; + BOOL target1_drawn = NO; +#endif + BOOL utf_flag = (BOOL) IS_UTF8_TTY; + BOOL hl1_drawn = NO; + + tmp[0] = tmp[1] = tmp[2] = '\0'; + + /* + * Bugs in the history code might cause -1 to be sent for cur, which yields + * a crash when LYStrNCpy() is called with a nonsense pointer. As far as I + * know, such bugs have been squashed, but if they should reappear, this + * works around them. -FM + */ + if (cur < 0) { + CTRACE((tfp, "LYhighlight cur %d (bug workaround)\n", cur)); + cur = 0; + } + + CTRACE((tfp, "LYhighlight at(%2d,%2d) %s %d [%d]:%s\n", + links[cur].ly, links[cur].lx, + (flag + ? "on" + : "off"), + cur, + links[cur].anchor_number, + NONNULL(target))); + +#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH) + if (flag == FALSE) + textinput_redrawn = FALSE; +#endif + + if (nlinks > 0) { +#ifdef USE_COLOR_STYLE + if (flag == TRUE || links[cur].type == WWW_FORM_LINK_TYPE) { + LYmove(LYP + title_adjust, LXP); + LynxChangeStyle(find_cached_style(cur, flag), STACK_ON); + } +#else + if (links[cur].type == WWW_FORM_LINK_TYPE + || LYGetHiliteStr(cur, 0) == NULL) { + LYMoveToLink(cur, target, NULL, + flag, links[cur].inUnderline, utf_flag); + lynx_start_link_color(flag == TRUE, links[cur].inUnderline); + } else { + LYMoveToLink(cur, target, LYGetHiliteStr(cur, 0), + flag, links[cur].inUnderline, utf_flag); + hl1_drawn = YES; +#ifdef SHOW_WHEREIS_TARGETS + target1_drawn = YES; +#endif + } +#endif + + if (links[cur].type == WWW_FORM_LINK_TYPE) { + int len; + int avail_space = (LYcolLimit - LXP) + (LYcolLimit * (LYlines - LYP)); + const char *text = LYGetHiliteStr(cur, 0); + + if (text == 0) + text = ""; + + if (avail_space > links[cur].l_form->size) + avail_space = links[cur].l_form->size; + + len = (int) (LYmbcs_skip_cells(text, avail_space, utf_flag) - text); + LYwaddnstr(LYwin, text, (size_t) len); + while (len++ < avail_space) + LYaddch('_'); + +#ifdef USE_COLOR_STYLE + } else if (flag == FALSE) { + redraw_lines_of_link(cur); + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.highlight.off: NOFIX branch @(%d,%d).\n", + LYP, LXP)); +#endif + } else if (!hl1_drawn) { + /* + * Copy into the buffer only what will fit within the width of the + * screen. + */ + LYmbcsstrncpy(buffer, + NonNull(LYGetHiliteStr(cur, 0)), + (int) (sizeof(buffer) - 1), + (LYcolLimit - LXP), + utf_flag); + LYaddstr(buffer); + } + + /* + * Display a second line as well. + */ + for (hi_count = 1; + (hi_string = LYGetHiliteStr(cur, hi_count)) != NULL + && LYP + hi_count <= display_lines; + ++hi_count) { + int row = LYP + hi_count + title_adjust; + + hi_offset = LYGetHilitePos(cur, hi_count); + if (hi_offset < 0) + continue; + lynx_stop_link_color(flag == TRUE, links[cur].inUnderline); + LYmove(row, hi_offset); + +#ifdef USE_COLOR_STYLE + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.highlight.line2: @(%d,%d), style=%d.\n", + row, hi_offset, + flag == TRUE ? s_alink : s_a)); + LynxChangeStyle(flag == TRUE ? s_alink : s_a, ABS_ON); +#else + lynx_start_link_color(flag == TRUE, links[cur].inUnderline); +#endif + + for (i = 0; (tmp[0] = hi_string[i]) != '\0' + && (i + hi_offset) < LYcols; i++) { + if (!IsSpecialAttrChar(hi_string[i])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + if (IS_CJK_TTY && is8bits(tmp[0])) { + tmp[1] = hi_string[++i]; + LYaddstr(tmp); + tmp[1] = '\0'; + } else { + LYaddstr(tmp); + } + } + } + } + lynx_stop_link_color(flag == TRUE, links[cur].inUnderline); +#ifdef SHOW_WHEREIS_TARGETS + for (hi_count = target1_drawn ? 1 : 0; + LYGetHiliteStr(cur, hi_count) != NULL; + hi_count++) { + TargetEmphasisON = show_whereis_targets(flag, + cur, + hi_count, + target, + TargetEmphasisON, + utf_flag); + } + + if (!LYShowCursor) + /* + * Get cursor out of the way. + */ + LYHideCursor(); + else +#endif /* SHOW_WHEREIS_TARGETS */ + /* + * Never hide the cursor if there's no FANCY CURSES or SLANG. + */ + LYmove(LYP + title_adjust, ((LXP > 0) ? (LXP - 1) : 0)); + + if (flag) + LYrefresh(); + } + return; +} + +/* + * free_and_clear will free a pointer if it is non-zero and then set it to + * zero. + */ +void free_and_clear(char **pointer) +{ + if (*pointer) { + FREE(*pointer); + *pointer = 0; + } + return; +} + +/* + * Convert single or serial newlines to single spaces throughout a string + * (ignore newlines if the preceding character is a space) and convert tabs to + * single spaces. Don't ignore any explicit tabs or spaces if the condense + * argument is FALSE, otherwise, condense any serial spaces or tabs to one + * space. - FM + */ +void convert_to_spaces(char *string, + int condense) +{ + char *s = string; + char *ns; + BOOL last_is_space = FALSE; + + if (!s) + return; + + s = LYSkipNonBlanks(s); + ns = s; + + while (*s) { + switch (*s) { + case ' ': + case '\t': + if (!(condense && last_is_space)) + *(ns++) = ' '; + last_is_space = TRUE; + break; + + case '\r': + case '\n': + if (!last_is_space) { + *(ns++) = ' '; + last_is_space = TRUE; + } + break; + + default: + *(ns++) = *s; + last_is_space = FALSE; + break; + } + s++; + } + *ns = '\0'; + return; +} + +/* + * Strip trailing slashes from directory paths. + */ +char *strip_trailing_slash(char *dirname) +{ + int i; + + i = (int) strlen(dirname) - 1; + while (i >= 0 && dirname[i] == '/') + dirname[i--] = '\0'; + return (dirname); +} + +/* + * Remove most blanks, but restore one trailing blank to make prompts nicer. + */ +static void remove_most_blanks(char *buffer) +{ + int length = (int) strlen(buffer); + BOOL trailing = (BOOL) ((length != 0) && (buffer[length - 1] == ' ')); + + LYReduceBlanks(buffer); + if (trailing) + strcat(buffer, " "); +} + +/* + * Display (or hide) the status line. + */ +BOOLEAN mustshow = FALSE; + +void statusline(const char *text) +{ + char buffer[MAX_LINE]; + unsigned char *temp = NULL; + int max_length, len, i, j; + int at_lineno; + unsigned char k; + char *p; + char text_buff[MAX_LINE]; + + if (text == NULL) + return; + + /* + * Don't print statusline messages if dumping to stdout. + */ + if (dump_output_immediately) + return; + + /* + * Don't print statusline message if turned off. + */ + if (mustshow != TRUE) { + if (no_statusline == TRUE) { + return; + } + } + mustshow = FALSE; + + /* "LYNXDOWNLOAD://Method=-1/File=%s/SugFile=%s%s\">Save to disk</a>\n" */ + LYStrNCpy(text_buff, text, sizeof(text_buff) - 1); + p = StrChr(text_buff, '\n'); + if (p) + *p = '\0'; + + /* + * Deal with any CJK escape sequences and Kanji if we have a CJK character + * set selected, otherwise, strip any escapes. Also, make sure text is not + * longer than the statusline window. - FM + */ + max_length = (((LYcolLimit - 1) < (int) sizeof(buffer)) + ? (LYcolLimit - 1) + : (int) sizeof(buffer) - 1); + if ((text_buff[0] != '\0') && + (LYHaveCJKCharacterSet)) { + /* + * Translate or filter any escape sequences. - FM + */ + if ((temp = typecallocn(unsigned char, strlen(text_buff) + 1)) == NULL) + outofmem(__FILE__, "statusline"); + + if (kanji_code == EUC) { + TO_EUC((const unsigned char *) text_buff, temp); + } else if (kanji_code == SJIS) { +#ifdef KANJI_CODE_OVERRIDE + if (!LYRawMode || last_kcode == SJIS) + strcpy(temp, text_buff); + else + TO_SJIS((const unsigned char *) text_buff, temp); +#else + strcpy((char *) temp, text_buff); +#endif + } else { + for (i = 0, j = 0; text_buff[i]; i++) { + if (text_buff[i] != CH_ESC) { /* S/390 -- gil -- 2119 */ + temp[j++] = UCH(text_buff[i]); + } + } + temp[j] = '\0'; + } + + /* + * Deal with any newlines or tabs in the string. - FM + */ + remove_most_blanks((char *) temp); + + /* + * Handle the Kanji, making sure the text is not longer than the + * statusline window. - FM + */ + for (i = 0, j = 0, len = 0, k = '\0'; + temp[i] != '\0' && len < max_length; i++) { + if (k != '\0') { + buffer[j++] = (char) k; + buffer[j++] = (char) temp[i]; + k = '\0'; + len += 2; + } else if ((temp[i] & 0200) != 0) { + k = temp[i]; + } else { + buffer[j++] = (char) temp[i]; + len++; + } + } + buffer[j] = '\0'; + FREE(temp); + } else { + /* + * Deal with any newlines or tabs in the string. - FM + */ + remove_most_blanks(text_buff); +#ifdef WIDEC_CURSES + len = (int) strlen(text_buff); + if (len >= (int) (sizeof(buffer) - 1)) + len = (int) (sizeof(buffer) - 1); + LYStrNCpy(buffer, text_buff, len); + /* FIXME: a binary search might be faster */ + while (len > 0 && LYstrExtent(buffer, len, len) > max_length) + buffer[--len] = '\0'; +#else + /* + * Strip any escapes, and shorten text if necessary. Note that we + * don't deal with the possibility of UTF-8 characters in the string. + * This is unlikely, but if strings with such characters are used in + * LYMessages_en.h, a compilation symbol of HAVE_UTF8_STATUSLINES could + * be added there, and code added here for determining the displayed + * string length, as we do above for CJK. - FM + */ + for (i = 0, len = 0; text_buff[i] != '\0' && len < max_length; i++) { + if (text_buff[i] != CH_ESC) { /* S/390 -- gil -- 2119 */ + buffer[len++] = text_buff[i]; + } + } + buffer[len] = '\0'; +#endif + } + + /* + * Move to the desired statusline window and output the text highlighted. + * - FM + */ + if (LYStatusLine >= 0) { + if (LYStatusLine < LYlines - 1) { + at_lineno = LYStatusLine; + } else { + at_lineno = LYlines - 1; + } + } else if (user_mode == NOVICE_MODE) { + at_lineno = LYlines - 3; + } else { + at_lineno = LYlines - 1; + } + LYmove(at_lineno, 0); + LYclrtoeol(); + + if (buffer[0] != '\0') { + BOOLEAN has_CJK = FALSE; + + if (IS_CJK_TTY) { + for (i = 0; buffer[i] != '\0'; i++) { + if (buffer[i] & 0x80) { + has_CJK = TRUE; + break; + } + } + } + + if (has_CJK +#ifdef HAVE_UTF8_STATUSLINES + || IS_UTF8_TTY +#endif + ) { + LYrefresh(); + } +#ifndef USE_COLOR_STYLE + lynx_start_status_color(); + LYaddstr(buffer); + lynx_stop_status_color(); +#else + /* draw the status bar in the STATUS style */ + { + int y, x; + int a = ((StrNCmp(buffer, ALERT_FORMAT, ALERT_PREFIX_LEN) + || !hashStyles[s_alert].used) + ? s_status + : s_alert); + + LynxChangeStyle(a, STACK_ON); + LYaddstr(buffer); + wbkgdset(LYwin, + ((lynx_has_color && LYShowColor >= SHOW_COLOR_ON) + ? (chtype) hashStyles[a].color + : A_NORMAL) | ' '); + LYGetYX(y, x); + (void) x; + if (y == at_lineno) { + LYclrtoeol(); + } + if (!(lynx_has_color && LYShowColor >= SHOW_COLOR_ON)) + wbkgdset(LYwin, A_NORMAL | ' '); + else if (s_normal != NOSTYLE) + wbkgdset(LYwin, (chtype) (hashStyles[s_normal].color | ' ')); + else + wbkgdset(LYwin, (chtype) (displayStyles[DSTYLE_NORMAL].color | ' ')); + LynxChangeStyle(a, STACK_OFF); + } +#endif + } + LYrefresh(); + + return; +} + +static const char *novice_lines(int lineno) +{ + switch (lineno) { + case 0: + return NOVICE_LINE_TWO_A; + case 1: + return NOVICE_LINE_TWO_B; + case 2: + return NOVICE_LINE_TWO_C; + default: + return ""; + } +} + +static int lineno = 0; + +void toggle_novice_line(void) +{ + lineno++; + if (*novice_lines(lineno) == '\0') + lineno = 0; + return; +} + +void noviceline(int more_flag GCC_UNUSED) +{ + if (dump_output_immediately) + return; + + LYmove(LYlines - 2, 0); + LYclrtoeol(); + LYaddstr(NOVICE_LINE_ONE); + + LYParkCursor(); +#if defined(DIRED_SUPPORT ) && defined(OK_OVERRIDE) + if (lynx_edit_mode && !no_dired_support) + LYaddstr(DIRED_NOVICELINE); + else +#endif /* DIRED_SUPPORT && OK_OVERRIDE */ + + if (LYUseNoviceLineTwo) + LYaddstr(NOVICE_LINE_TWO); + else + LYaddstr(novice_lines(lineno)); + + LYrefresh(); + return; +} + +/* + * If the standard input is not a tty, and Lynx is really reading from the + * standard input, attempt to reopen it, pointing to a real tty. Normally + * this would happen if the user pipes data to Lynx and wants to run + * interactively after that. + * + * Returns: + * 1 if successfully reopened + * -1 if we cannot reopen + * 0 if we do not have to reopen + */ +int LYReopenInput(void) +{ + int result = 0; + int fd; + + if ((fd = fileno(stdin)) == 0 + && !isatty(fd) + && LYConsoleInputFD(FALSE) == fd) { + const char *term_name = NULL; + int new_fd = -1; + +#ifdef HAVE_TTYNAME + if (isatty(fileno(stdout)) && + (term_name = ttyname(fileno(stdout))) != NULL) + new_fd = open(term_name, O_RDONLY); + + if (new_fd == -1 && + isatty(fileno(stderr)) && + (term_name = ttyname(fileno(stderr))) != NULL) + new_fd = open(term_name, O_RDONLY); +#endif + +#ifdef HAVE_CTERMID + if (new_fd == -1 && + (term_name = ctermid(NULL)) != NULL) + new_fd = open(term_name, O_RDONLY); +#endif + +#ifdef TTY_DEVICE + if (new_fd == -1) + new_fd = open(term_name = TTY_DEVICE, O_RDONLY); +#endif + + CTRACE((tfp, "LYReopenInput open(%s) returned %d.\n", term_name, new_fd)); + if (new_fd >= 0) { + FILE *frp; + + close(new_fd); + frp = freopen(term_name, "r", stdin); + CTRACE((tfp, + "LYReopenInput freopen(%s,\"r\",stdin) returned %p, stdin is now %p with fd %d.\n", + term_name, (void *) frp, (void *) stdin, fileno(stdin))); + result = 1; + } else { + result = -1; + } + } + return result; +} + +/* + * Returns the file descriptor from which keyboard input is expected, or INVSOC + * (-1) if not available. If need_selectable is true, returns non-INVSOC fd + * only if select() is possible - actually, currently only checks if fd is + * connected to a tty. - kw + */ +int LYConsoleInputFD(int need_selectable) +{ + int fd = INVSOC; + +#ifdef USE_SLANG + if (!LYCursesON) + fd = fileno(stdin); +#if ((SLANG_VERSION >= 9919) && defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__)) + /* SLang_TT_Read_FD introduced in slang 0.99.19, from its changelog: + * SLang_TT_Read_FD variable is now available for unix. This is the file + * descriptor used by SLang_getkey. */ + else + fd = SLang_TT_Read_FD; +#endif /* SLANG_VERSION >= 9919 */ +#else /* !USE_SLANG */ + fd = fileno(stdin); +#endif /* !USE_SLANG */ + + if (need_selectable && fd != INVSOC) { + if (isatty(fd)) { + return fd; + } else { + return INVSOC; + } + } + return fd; +} + +static int fake_zap = 0; + +void LYFakeZap(int set) +{ + if (set && fake_zap < 1) { + CTRACE((tfp, "\r *** Set simulated 'Z'")); + if (fake_zap) + CTRACE((tfp, ", %d pending", fake_zap)); + CTRACE((tfp, " ***\n")); + fake_zap++; + } else if (!set && fake_zap) { + CTRACE((tfp, "\r *** Unset simulated 'Z'")); + CTRACE((tfp, ", %d pending", fake_zap)); + CTRACE((tfp, " ***\n")); + fake_zap = 0; + } + +} + +static int DontCheck(void) +{ + static time_t last; + time_t next; + + /** Curses or slang setup was not invoked **/ + if (dump_output_immediately) + return (TRUE); + + if (LYHaveCmdScript()) /* we may be running from a script */ + return (TRUE); + + if (LYNoZapKey) + return (TRUE); + /* + * Avoid checking interrupts more than one per second, since it is a slow + * and expensive operation - TD + */ +#ifdef HAVE_GETTIMEOFDAY +#undef timezone /* U/Win defines a conflicting macro */ + { + struct timeval tv; + + gettimeofday(&tv, (struct timezone *) 0); + next = (tv.tv_sec * 10); + next += (tv.tv_usec / 100000L); /* 0.1 seconds is a compromise */ + } +#else + next = time((time_t *) 0); +#endif + if (next == last) + return (TRUE); + + last = next; + return FALSE; +} + +int HTCheckForInterrupt(void) +{ + int c; + int cmd; + + if (fake_zap > 0) { + fake_zap--; + CTRACE((tfp, "\r *** Got simulated 'Z' ***\n")); + CTRACE_FLUSH(tfp); + CTRACE_SLEEP(AlertSecs); + return ((int) TRUE); + } + + /** Curses or slang setup was not invoked **/ + if (DontCheck()) + return ((int) FALSE); + +#ifndef VMS /* UNIX stuff: */ + +#if !defined(_WINDOWS) || defined(__MINGW32__) + + /* + * First, check if there is a character. + */ +#ifdef USE_SLANG + /** No keystroke was entered + Note that this isn't taking possible SOCKSification + and the socks_flag into account, and may fail on the + slang library's select() when SOCKSified. - FM **/ +#ifdef DJGPP_KEYHANDLER + if (0 == _bios_keybrd(_NKEYBRD_READY)) + return (FALSE); +#else + if (0 == SLang_input_pending(0)) + return (FALSE); +#endif /* DJGPP_KEYHANDLER */ + +#else /* Unix curses: */ + { + struct timeval socket_timeout; + int ret = 0; + fd_set readfds; + + socket_timeout.tv_sec = 0; + socket_timeout.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(0, &readfds); +#ifdef SOCKS + if (socks_flag) + ret = Rselect(1, &readfds, NULL, NULL, &socket_timeout); + else +#endif /* SOCKS */ + ret = select(1, &readfds, NULL, NULL, &socket_timeout); + + /** Suspended? **/ + if ((ret == -1) && (SOCKET_ERRNO == EINTR)) + return ((int) FALSE); + + /** No keystroke was entered? **/ + if (!FD_ISSET(0, &readfds)) + return ((int) FALSE); + } +#endif /* USE_SLANG */ + +#endif /* !_WINDOWS */ + + /* + * Now, read the character. + */ +#if defined(USE_CURSES_NODELAY) + nodelay(LYwin, TRUE); + c = LYgetch(); + nodelay(LYwin, FALSE); +#elif defined(USE_SLANG) && defined(_WINDOWS) + if (!SLang_input_pending(0)) + return ((int) FALSE); + c = LYgetch(); +#else + c = LYgetch(); +#endif + +#else /* VMS: */ + extern int typeahead(void); + + /** Control-C or Control-Y and a 'N'o reply to exit query **/ + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + return ((int) TRUE); + } + + c = typeahead(); + +#endif /* !VMS */ + + /* + * 'c' contains whatever character we're able to read from keyboard + */ + + /** Keyboard 'Z' or 'z', or Control-G or Control-C **/ + if (LYCharIsINTERRUPT(c)) + return ((int) TRUE); + + /* There is a subset of mainloop() actions available at this stage: no new + * getfile() cycle is possible until the previous finished. Currently we + * have scrolling in partial mode, toggling of trace log, and pasting. + * User search now in progress... + */ + cmd = (LKC_TO_LAC(keymap, c)); + switch (cmd) { + case LYK_TRACE_TOGGLE: /* Toggle TRACE mode. */ + handle_LYK_TRACE_TOGGLE(); + break; +#ifdef CAN_CUT_AND_PASTE + case LYK_TO_CLIPBOARD:{ /* ^S */ + const char *s = LYDownLoadAddress(); + + if (!s || !*s || put_clip(s)) + HTInfoMsg(gettext("Copy to clipboard failed.")); + else + HTInfoMsg(gettext("Download document URL put to clipboard.")); + break; + } +#endif /* defined CAN_CUT_AND_PASTE */ + default: +#ifdef DISP_PARTIAL + /* OK, we got several lines from new document and want to scroll... */ + if (display_partial && (NumOfLines_partial > 2)) { + BOOLEAN do_refresh; + int res; + int Newline_partial = LYGetNewline(); + + switch (cmd) { + case LYK_WHEREIS: /* search within the document */ + case LYK_NEXT: /* search for the next occurrence in the document */ + case LYK_PREV: /* search for the previous occurrence in the document */ + handle_LYK_WHEREIS(cmd, &do_refresh); + if (www_search_result != -1) { + Newline_partial = www_search_result; + www_search_result = -1; /* reset */ + } + break; + + case LYK_FASTBACKW_LINK: + if (Newline_partial <= (display_lines) + 1) { + Newline_partial -= display_lines; + } else if ((res = + HTGetLinkOrFieldStart(-1, + &Newline_partial, NULL, + -1, TRUE)) == LINK_LINE_FOUND) { + Newline_partial++; + } else if (res == LINK_DO_ARROWUP) { + Newline_partial -= display_lines; + } + break; + case LYK_FASTFORW_LINK: + if (HText_canScrollDown()) { + /* This is not an exact science... - kw */ + if (HTGetLinkOrFieldStart(HText_LinksInLines(HTMainText, + Newline_partial, + display_lines) + - 1, + &Newline_partial, NULL, + 1, TRUE) == LINK_LINE_FOUND) { + Newline_partial++; + } + } + break; + case LYK_PREV_PAGE: + if (Newline_partial > 1) + Newline_partial -= display_lines; + break; + case LYK_NEXT_PAGE: + if (HText_canScrollDown()) + Newline_partial += display_lines; + break; + case LYK_UP_HALF: + if (Newline_partial > 1) + Newline_partial -= (display_lines / 2); + break; + case LYK_DOWN_HALF: + if (HText_canScrollDown()) + Newline_partial += (display_lines / 2); + break; + case LYK_UP_TWO: + if (Newline_partial > 1) + Newline_partial -= 2; + break; + case LYK_DOWN_TWO: + if (HText_canScrollDown()) + Newline_partial += 2; + break; + case LYK_HOME: + if (Newline_partial > 1) + Newline_partial = 1; + break; + case LYK_END: + if (HText_canScrollDown()) + Newline_partial = HText_getNumOfLines() - display_lines + 1; + /* calculate for "current" bottom value */ + break; + case LYK_REFRESH: + break; + default: + /** Other or no keystrokes **/ + return ((int) FALSE); + } /* end switch */ + if (Newline_partial < 1) + Newline_partial = 1; + if (LYMainLoop_pageDisplay(Newline_partial)) + NumOfLines_partial = HText_getNumOfLines(); + } +#endif /* DISP_PARTIAL */ + break; + } /* end switch */ + /** Other or no keystrokes **/ + return ((int) FALSE); +} + +/* + * Check if the given filename looks like it's an absolute pathname, i.e., + * references a directory. + */ +BOOLEAN LYisAbsPath(const char *path) +{ + BOOLEAN result = FALSE; + + if (non_empty(path)) { +#ifdef VMS + result = TRUE; +#else +#if defined(USE_DOS_DRIVES) + result = (BOOLEAN) (LYIsPathSep(path[0]) + || (LYIsDosDrive(path) + && LYIsPathSep(path[2]))); +#else + result = (BOOLEAN) (LYIsPathSep(path[0])); +#endif /* USE_DOS_DRIVES */ +#endif + } + return result; +} + +/* + * Check if the given filename is the root path, e.g., "/" on Unix. + */ +BOOLEAN LYisRootPath(const char *path) +{ +#if defined(USE_DOS_DRIVES) + if (strlen(path) == 3 + && LYIsDosDrive(path) + && LYIsPathSep(path[2])) + return TRUE; +#endif + return (BOOL) ((strlen(path) == 1) && LYIsPathSep(path[0])); +} + +/* + * A file URL for a remote host is an obsolete ftp URL. + * Return YES only if we're certain it's a local file. - FM + */ +BOOLEAN LYisLocalFile(const char *filename) +{ + char *host = NULL; + char *acc_method = NULL; + char *cp; + + if (!filename) + return NO; + if (!(host = HTParse(filename, "", PARSE_HOST))) + return NO; + if (!*host) { + FREE(host); + return NO; + } + + if ((cp = StrChr(host, ':')) != NULL) + *cp = '\0'; + + if ((acc_method = HTParse(filename, "", PARSE_ACCESS))) { + if (0 == strcmp("file", acc_method) && + (0 == strcmp(host, "localhost") || + LYSameFilename(host, HTHostName()))) { + FREE(host); + FREE(acc_method); + return YES; + } + } + + FREE(host); + FREE(acc_method); + return NO; +} + +/* + * Utility for checking URLs with a host field. Return YES only if we're + * certain it's the local host. - FM + */ +BOOLEAN LYisLocalHost(const char *filename) +{ + char *host = NULL; + char *cp; + + if (!filename) + return NO; + if (!(host = HTParse(filename, "", PARSE_HOST))) + return NO; + if (!*host) { + FREE(host); + return NO; + } + + if ((cp = StrChr(host, ':')) != NULL) + *cp = '\0'; + + if ((LYSameFilename(host, "localhost") || + LYSameFilename(host, LYHostName) || + LYSameFilename(host, HTHostName()))) { + FREE(host); + return YES; + } + + FREE(host); + return NO; +} + +/* + * Free an HTList that contains strings. + */ +void LYFreeStringList(HTList *list) +{ + if (list != NULL) { + char *argument; + HTList *cur = list; + + while (NULL != (argument = (char *) HTList_nextObject(cur))) { + FREE(argument); + } + HTList_delete(list); + } +} + +/* + * Utility for freeing the list of local host aliases. - FM + */ +void LYLocalhostAliases_free(void) +{ + LYFreeStringList(localhost_aliases); + localhost_aliases = NULL; +} + +/* + * Utility for listing hosts to be treated as local aliases. - FM + */ +void LYAddLocalhostAlias(char *alias) +{ + char *LocalAlias = NULL; + + if (!non_empty(alias)) + return; + + if (!localhost_aliases) { + localhost_aliases = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(LYLocalhostAliases_free); +#endif + } + + StrAllocCopy(LocalAlias, alias); + HTList_addObject(localhost_aliases, LocalAlias); + + return; +} + +/* + * Utility for checking URLs with a host field. Return YES only if we've + * listed the host as a local alias. - FM + */ +BOOLEAN LYisLocalAlias(const char *filename) +{ + char *host = NULL; + char *alias; + char *cp; + HTList *cur = localhost_aliases; + + if (!cur || !filename) + return NO; + if (!(host = HTParse(filename, "", PARSE_HOST))) + return NO; + if (!(*host)) { + FREE(host); + return NO; + } + + if ((cp = StrChr(host, ':')) != NULL) + *cp = '\0'; + + while (NULL != (alias = (char *) HTList_nextObject(cur))) { + if (LYSameFilename(host, alias)) { + FREE(host); + return YES; + } + } + + FREE(host); + return NO; +} + +/* + * This function checks for a URL with an unknown scheme, + * but for which proxying has been set up, and if so, + * returns PROXY_URL_TYPE. - FM + * + * If a colon is present but the string segment which + * precedes it is not being proxied, and we can be sure + * that what follows the colon is not a port field, + * it returns UNKNOWN_URL_TYPE. Otherwise, it returns + * 0 (not a URL). - FM + */ +UrlTypes LYCheckForProxyURL(char *filename) +{ + char *cp = filename; + char *cp1; + char *cp2 = NULL; + + /* + * Don't crash on an empty argument. + */ + if (isEmpty(cp)) + return (NOT_A_URL_TYPE); + + /* kill beginning spaces */ + cp = LYSkipBlanks(cp); + + /* + * Check for a colon, and if present, + * see if we have proxying set up. + */ + if ((cp1 = StrChr((cp + 1), ':')) != NULL) { + if ((cp2 = StrChr((cp + 1), '/')) != NULL && cp2 < cp1) + return (NOT_A_URL_TYPE); + *cp1 = '\0'; + cp2 = NULL; + StrAllocCopy(cp2, cp); + *cp1 = ':'; + StrAllocCat(cp2, "_proxy"); + if (LYGetEnv(cp2) != NULL) { + FREE(cp2); + return (PROXY_URL_TYPE); + } + FREE(cp2); +#if defined (USE_DOS_DRIVES) + if (LYIsDosDrive(cp)) + return (NOT_A_URL_TYPE); +#endif + cp1++; + if (!*cp) { + return (NOT_A_URL_TYPE); + } else if (isdigit(UCH(*cp1))) { + while (*cp1 && isdigit(UCH(*cp1))) + cp1++; + if (*cp1 && !LYIsHtmlSep(*cp1)) + return (UNKNOWN_URL_TYPE); + } else { + return (UNKNOWN_URL_TYPE); + } + } + + return (NOT_A_URL_TYPE); +} + +/* + * Compare a "type:" string, replacing it by the comparison-string if it + * matches (and return true in that case). + */ +static BOOLEAN compare_type(char *tst, + const char *cmp, + size_t len) +{ + if (!strncasecomp(tst, cmp, (int) len)) { + if (StrNCmp(tst, cmp, len)) { + size_t i; + + for (i = 0; i < len; i++) + tst[i] = cmp[i]; + } + return TRUE; + } + return FALSE; +} +#define CompareType(tst,cmp,len) compare_type((tst),(cmp),(size_t)(len)) + +#define DoubleHtmlSep(s) (LYIsHtmlSep((s)[0]) && LYIsHtmlSep((s)[1])) +#define compare_two(tst,cmp,len,limit) \ + ((len + 2) <= limit \ + && DoubleHtmlSep(tst + len) \ + && CompareType(tst, cmp, len)) + +/* + * Must recognize a URL and return the type. If recognized, based on a + * case-insensitive analysis of the scheme field, ensures that the scheme field + * has the expected case. + * + * Returns 0 (not a URL) for a NULL argument, one which lacks a colon. + * + * Chains to LYCheckForProxyURL() if a colon is present but the type is not + * recognized. + */ +UrlTypes is_url(char *filename) +{ + char *cp = filename; + char *cp1; + UrlTypes result = NOT_A_URL_TYPE; + int limit; + + /* + * Don't crash on an empty argument. + */ + if (isEmpty(cp)) + return (result); + + /* + * Can't be a URL if it lacks a colon and if it starts with '[' it's + * probably IPv6 address. + */ + if (NULL == StrChr(cp, ':') || cp[0] == '[') + return (result); + + /* + * Kill beginning spaces. + */ + cp = LYSkipBlanks(cp); + + /* + * Can't be a URL if it starts with a slash. So return immediately for + * this common case, also to avoid false positives if there was a colon + * later in the string. Also can't be a URL if it starts with a colon. - + * KW + */ + if (*cp == ':' || LYIsHtmlSep(*cp)) { + result = NOT_A_URL_TYPE; + + } else { + limit = (int) strlen(cp); + switch (*cp) { + case 'L': + case 'l': + /* + * Lynx internal pages ("LYNXfoo:" or "lynxfoo:") start with 'l' or + * 'L', other URLs aren't. + */ + if (CompareType(cp, STR_LYNXEXEC, LEN_LYNXEXEC)) { + /* + * Special External Lynx type to handle execution of commands + * or scripts which require a pause to read the screen upon + * completion. + */ + result = LYNXEXEC_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXPROG, LEN_LYNXPROG)) { + /* + * Special External Lynx type to handle execution of commands, + * scripts or programs which do not require a pause to read + * screen upon completion. + */ + result = LYNXPROG_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXCGI, LEN_LYNXCGI)) { + result = LYNXCGI_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXPRINT, LEN_LYNXPRINT)) { + result = LYNXPRINT_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)) { + result = LYNXOPTIONS_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXCFG, LEN_LYNXCFG)) { + result = LYNXCFG_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXMESSAGES, LEN_LYNXMESSAGES)) { + result = LYNXMESSAGES_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXCFLAGS, LEN_LYNXCFLAGS)) { + result = LYNXCOMPILE_OPTS_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXDOWNLOAD, LEN_LYNXDOWNLOAD)) { + result = LYNXDOWNLOAD_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXDIRED, LEN_LYNXDIRED)) { + result = LYNXDIRED_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXEDITMAP, LEN_LYNXEDITMAP)) { + result = LYNXEDITMAP_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXHIST, LEN_LYNXHIST)) { + result = LYNXHIST_URL_TYPE; + +#ifdef USE_CACHEJAR + } else if (CompareType(cp, STR_LYNXCACHE, LEN_LYNXCACHE)) { + result = LYNXCACHE_URL_TYPE; +#endif + } else if (CompareType(cp, STR_LYNXKEYMAP, LEN_LYNXKEYMAP)) { + result = LYNXKEYMAP_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXIMGMAP, LEN_LYNXIMGMAP)) { + /* force lower/uppercase of next part */ + (void) is_url(&cp[LEN_LYNXIMGMAP]); + result = LYNXIMGMAP_URL_TYPE; + + } else if (CompareType(cp, STR_LYNXCOOKIE, LEN_LYNXCOOKIE)) { + result = LYNXCOOKIE_URL_TYPE; + + } + break; +#ifndef DISABLE_NEWS + /* + * NEWSfoo: schemes - + */ + case 'N': + case 'n': + if (CompareType(cp, STR_NEWS_URL, LEN_NEWS_URL)) { + result = NEWS_URL_TYPE; + + } else if (CompareType(cp, STR_NNTP_URL, LEN_NNTP_URL)) { + result = NNTP_URL_TYPE; + + } else if (CompareType(cp, "newspost:", 9)) { + /* + * Special Lynx type to handle news posts. + */ + result = NEWSPOST_URL_TYPE; + + } else if (CompareType(cp, "newsreply:", 10)) { + /* + * Special Lynx type to handle news replies (followups). + */ + result = NEWSREPLY_URL_TYPE; + } + break; + + /* + * SNEWSfoo: schemes - + */ + case 'S': + case 's': + if (CompareType(cp, STR_SNEWS_URL, LEN_SNEWS_URL)) { + result = SNEWS_URL_TYPE; + + } else if (CompareType(cp, "snewspost:", 10)) { + /* + * Special Lynx type to handle snews posts. + */ + result = NEWSPOST_URL_TYPE; + + } else if (CompareType(cp, "snewsreply:", 11)) { + /* + * Special Lynx type to handle snews replies (followups). + */ + result = NEWSREPLY_URL_TYPE; + } + break; +#endif + case 'M': + case 'm': + if (CompareType(cp, STR_MAILTO_URL, LEN_MAILTO_URL)) { + result = MAILTO_URL_TYPE; + } + break; + + case 'F': + case 'f': + if (CompareType(cp, STR_FILE_URL, LEN_FILE_URL)) { + if (LYisLocalFile(cp)) { + result = FILE_URL_TYPE; + } else if (DoubleHtmlSep(cp + LEN_FILE_URL)) { + result = FTP_URL_TYPE; + } + } +#ifndef DISABLE_FTP + else if (compare_two(cp, STR_FTP_URL, LEN_FTP_URL, limit)) { + result = FTP_URL_TYPE; + } +#endif +#ifndef DISABLE_FINGER + else if (compare_two(cp, STR_FINGER_URL, LEN_FINGER_URL, limit)) { + result = FINGER_URL_TYPE; + } +#endif + break; + + case 'B': + case 'b': +#ifndef DISABLE_BIBP + if (CompareType(cp, STR_BIBP_URL, LEN_BIBP_URL)) { + result = BIBP_URL_TYPE; + } +#endif + break; + + case 'D': + case 'd': + if (CompareType(cp, "data:", 5)) { + result = DATA_URL_TYPE; + } + break; + + default: + if (limit >= 3 + && ((cp1 = StrChr(cp + 3, ':')) == NULL + || !DoubleHtmlSep(cp1 + 1))) { + /* + * If it doesn't contain "://", and it's not one of the the + * above, it can't be a URL with a scheme we know, so check if + * it's an unknown scheme for which proxying has been set up. + * - FM + */ + if (cp1 != NULL + && (cp1 - cp) > 1 /* exclude DOS-style device:/path */ + && LYisAbsPath(cp1 + 1)) { + result = NCFTP_URL_TYPE; + } + + } else { + switch (*cp) { + case 'H': + case 'h': + if (CompareType(cp, STR_HTTP_URL, LEN_HTTP_URL)) { + result = HTTP_URL_TYPE; + + } else if (CompareType(cp, STR_HTTPS_URL, LEN_HTTPS_URL)) { + result = HTTPS_URL_TYPE; + } + break; + +#ifndef DISABLE_GOPHER + case 'G': + case 'g': + if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) { + if (strlen(cp) >= 11 + && (cp1 = StrChr(cp + 11, '/')) != NULL) { + + if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w') + /* if this is a gopher html type */ + result = HTML_GOPHER_URL_TYPE; + else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8') + result = TELNET_GOPHER_URL_TYPE; + else if (*(cp1 + 1) == '7') + result = INDEX_GOPHER_URL_TYPE; + else + result = GOPHER_URL_TYPE; + } else { + result = GOPHER_URL_TYPE; + } + } + break; +#endif + case 'W': + case 'w': + if (CompareType(cp, STR_WAIS_URL, LEN_WAIS_URL)) { + result = WAIS_URL_TYPE; + } + break; + + case 'T': + case 't': + if (CompareType(cp, STR_TELNET_URL, LEN_TELNET_URL)) { + result = TELNET_URL_TYPE; + + } else if (CompareType(cp, STR_TN3270_URL, LEN_TN3270_URL)) { + result = TN3270_URL_TYPE; + } + break; + + case 'R': + case 'r': + if (CompareType(cp, STR_RLOGIN_URL, LEN_RLOGIN_URL)) { + result = RLOGIN_URL_TYPE; + } + break; + + case 'C': + case 'c': + if (CompareType(cp, STR_CSO_URL, LEN_CSO_URL)) { + result = CSO_URL_TYPE; + } + break; + + case 'A': + case 'a': + if (CompareType(cp, "afs:", 4)) { + result = AFS_URL_TYPE; + } + break; + + case 'P': + case 'p': + if (CompareType(cp, "prospero:", 9)) { + result = PROSPERO_URL_TYPE; + } + break; + } + } + } + /* + * Check if it is an unknown scheme for which proxying has been set up. + */ + if (result == NOT_A_URL_TYPE) + result = LYCheckForProxyURL(filename); + } + return result; +} + +/* + * Sometimes it is just expected that curses is on when an alert or other + * statusline message needs to be shown and we are not just dumping + * immediately. Calling this will 'fix' it, but may not always be appropriate. + * - kw + */ +void LYFixCursesOn(const char *reason) +{ + if (dump_output_immediately || LYCursesON) + return; + if (reason) { + CTRACE((tfp, "Forcing curses on to %s\n", reason)); + } + start_curses(); +} + +/* + * Most protocol modules called through HTLoad* expect that curses is on unless + * dump_output_immediately is set, so that statusline messages can be shown. + * Some protocols expect the opposite, namely telnet and friends. This + * function should be called after the 'physical' URL for accessing addr has + * been established. It does the right thing to the degree that curses is + * turned on for known problem cases. In any normal circumstances this should + * never apply, but proxying or rule substitution is not prevented for + * telnet-like URLs, and this 'fix' avoids some crashes that can otherwise + * occur. - kw + */ +BOOLEAN LYFixCursesOnForAccess(const char *addr, + const char *physical) +{ + /* + * If curses is off when maybe it shouldn't... + */ + if (!dump_output_immediately && !LYCursesON && physical) { + char *cp1; + + /* + * If requested resource wants to be accessed with curses off, and + * getfile() would indeed have turned curses off for it... + */ + if (strstr(addr, "://") != NULL && + (isTELNET_URL(addr) || + isRLOGIN_URL(addr) || + isTN3270_URL(addr) || + (!isGOPHER_URL(addr) && + (cp1 = StrChr(addr + 11, '/')) != NULL && + (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')))) { + /* + * If actual access that will be done is ok with curses off, then + * do nothing special, else force curses on. - kw + */ + if (!isTELNET_URL(physical) && + !isRLOGIN_URL(physical) && + !isTN3270_URL(physical)) { + start_curses(); + HTAlert(gettext("Unexpected access protocol for this URL scheme.")); + return TRUE; + } + } + } + return FALSE; +} + +/* + * Determine whether we allow HEAD and related flags for a URL. - kw + */ +BOOLEAN LYCanDoHEAD(const char *address) +{ + char *temp0 = NULL; + int isurl; + + if (!non_empty(address)) + return FALSE; + if (!StrNCmp(address, "http", 4)) + return TRUE; + /* Make copy for is_url() since caller may not care for case changes */ + StrAllocCopy(temp0, address); + isurl = is_url(temp0); + if (!isurl) { + FREE(temp0); + return FALSE; + } + if (isurl == LYNXCGI_URL_TYPE) { + FREE(temp0); +#if defined(LYNXCGI_LINKS) && !defined(VMS) + return TRUE; +#else + return FALSE; +#endif + } + /* + * The idea of the following is to allow HEAD for news URLs that identify + * single articles, not those that identify ranges of articles or groups or + * a list of groups. - kw + */ + if (isurl == NEWS_URL_TYPE || isurl == NNTP_URL_TYPE) { + char *temp = HTParse(address, "", PARSE_PATH); + char *cp = strrchr(temp, '/'); + + if (StrChr((cp ? cp : temp), '@') != NULL) { + FREE(temp0); + FREE(temp); + return TRUE; + } + if (cp && isdigit(UCH(cp[1])) && StrChr(cp, '-') == NULL) { + FREE(temp0); + FREE(temp); + return TRUE; + } + FREE(temp); + } +#define ALLOW_PROXY_HEAD +/* If defined, also allow head requests for URLs proxied through the "http" or + * "lynxcgi" protocols, which understand HEAD. Only the proxy environment + * variables are checked, not the HTRules system. - kw + */ +#ifdef ALLOW_PROXY_HEAD + if (isurl != FILE_URL_TYPE) { + char *acc_method = HTParse(temp0, "", PARSE_ACCESS); + + if (non_empty(acc_method)) { + char *proxy; + + StrAllocCat(acc_method, "_proxy"); + proxy = LYGetEnv(acc_method); + if (proxy && (isHTTP_URL(proxy) || + isLYNXCGI(proxy)) && + !override_proxy(temp0)) { + FREE(temp0); + FREE(acc_method); + return TRUE; + } + } + FREE(acc_method); + } +#endif /* ALLOW_PROXY_HEAD */ + + FREE(temp0); + return FALSE; +} + +/* + * Close an input file. + */ +BOOLEAN LYCloseInput(FILE *fp) +{ + int result = FALSE; + + if (fp != 0) { + int err = ferror(fp); + LY_TEMP *p = FindTempfileByFP(fp); + + fclose(fp); + if (p != 0) { + p->file = 0; + } + if (!err) { + result = TRUE; + } + } + return (BOOLEAN) result; +} + +/* + * Close an output file, reporting any problems with writing to it. + */ +BOOLEAN LYCloseOutput(FILE *fp) +{ + int result = FALSE; + + if (fp != 0) { + int err = ferror(fp); + LY_TEMP *p = FindTempfileByFP(fp); + + fclose(fp); + if (p != 0) { + p->file = 0; + } + if (!err) { + result = TRUE; + } + } + if (!result) { + HTAlert(CANNOT_WRITE_TO_FILE); + } + return (BOOLEAN) result; +} + +/* + * Test if we'll be able to write a file. If not, warn the user. + */ +BOOLEAN LYCanWriteFile(const char *filename) +{ + BOOLEAN result = FALSE; + + if (LYCloseOutput(fopen(filename, "w"))) { + if (remove(filename) == 0) { + result = TRUE; + } + } else { + _statusline(NEW_FILENAME_PROMPT); + } + return result; +} + +/* + * Test if we'll be able to read a file. + */ +BOOLEAN LYCanReadFile(const char *filename) +{ + FILE *fp; + BOOLEAN result = FALSE; + + if (non_empty(filename)) { + if ((fp = fopen(filename, "r")) != 0) { + result = LYCloseInput(fp); + } + } + return result; +} + +char *LYFindConfigFile(const char *nominal, const char *dftfile) +{ + char *result = 0; + char *path = 0; + char *head = 0; + char *leaf; + char *item; + + if (non_empty(nominal)) { + StrAllocCopy(result, nominal); + + /* + * Look for it in as-is - first expanding any tilde. + */ + LYTildeExpand(&result, TRUE); + if (!LYCanReadFile(result)) { + const char *cfg_path; + char *list = 0; + BOOLEAN found = FALSE; + + /* + * Now try in the config-path. + */ + if ((cfg_path = LYGetEnv("LYNX_CFG_PATH")) == NULL) + cfg_path = LYNX_CFG_PATH; + + StrAllocCopy(list, cfg_path); + path = list; + while ((item = LYstrsep(&path, PATH_SEPARATOR)) != 0) { + if (isEmpty(item)) + continue; + FREE(result); + HTSprintf0(&result, "%s%s%s", item, FILE_SEPARATOR, nominal); + LYTildeExpand(&result, TRUE); + if (LYCanReadFile(result)) { + found = TRUE; + break; + } + } + FREE(list); + + if (!found) { + /* + * If not found, try finding it in the same directory as the + * compiled-in location of the default file. + */ + StrAllocCopy(head, dftfile); + if (strcmp(nominal, dftfile) && + (leaf = LYPathLeaf(head)) != head) { + + head[leaf - head] = '\0'; + StrAllocCopy(result, head); + StrAllocCat(result, nominal); + + if (!LYCanReadFile(result)) { + FREE(result); + } + } +#ifdef USE_PROGRAM_DIR + else { + /* + * Finally, try in the same directory as the executable. + */ + StrAllocCopy(result, program_dir); + LYAddPathSep(&result); + StrAllocCat(result, nominal); + LYTildeExpand(&result, TRUE); + if (!LYCanReadFile(result)) { + FREE(result); + } + } +#endif + } + } + + } + FREE(head); + return result; +} + +/* + * Remove backslashes from any string. + */ +void remove_backslashes(char *buf) +{ + char *cp; + + for (cp = buf; *cp != '\0'; cp++) { + + if (*cp != '\\') { /* don't print slashes */ + *buf = *cp; + buf++; + } else if (*cp == '\\' && /* print one slash if there */ + *(cp + 1) == '\\') { /* are two in a row */ + *buf = *cp; + buf++; + } + } + *buf = '\0'; + return; +} + +/* + * Checks to see if the current process is attached via a terminal in the local + * domain. + */ +BOOLEAN inlocaldomain(void) +{ + BOOLEAN result = TRUE; + +#ifdef HAVE_UTMP + int n; + FILE *fp; + struct utmp me; + char *cp, *mytty = NULL; + + if ((cp = ttyname(0))) { + mytty = cp; + if (!strncmp(mytty, "/dev/", 5)) { + mytty += 5; /* pty's can be like "pts/0" in utmp */ + } else { + if ((mytty = LYLastPathSep(cp)) != 0) + ++mytty; + } + } + + result = FALSE; + if (mytty && (fp = fopen(UTMP_FILE, "r")) != NULL) { + size_t ulen = strlen(mytty); + + if (ulen > sizeof(me.ut_line)) + ulen = sizeof(me.ut_line); + do { + n = (int) fread((char *) &me, sizeof(struct utmp), (size_t) 1, fp); + + if (n <= 0) + break; + } while (memcmp(me.ut_line, mytty, ulen)); + (void) LYCloseInput(fp); + + if (n > 0) { + for (ulen = 0; ulen < sizeof(me.ut_host); ++ulen) { + if (me.ut_host[ulen] == '\0') + break; + } + if (ulen > strlen(LYLocalDomain) && + !memcmp(LYLocalDomain, + me.ut_host + ulen - strlen(LYLocalDomain), + strlen(LYLocalDomain))) { + result = TRUE; + } +#ifdef LINUX + /* Linux fix to check for local user. J.Cullen 11Jul94 */ + else if (ulen == 0) { + result = TRUE; + } +#endif /* LINUX */ + } + + } else { + CTRACE((tfp, + "Could not get ttyname (returned %s) or open UTMP file %s\n", + NONNULL(cp), UTMP_FILE)); + } +#else + CTRACE((tfp, "LYUtils: inlocaldomain() not supported.\n")); +#endif /* HAVE_UTMP */ + return (result); +} + +#ifdef HAVE_SIGACTION +/* + * An extended alternative for calling signal(), sets some flags for signal + * handler as we want them if that functionality is available. (We don't + * return anything from this function since the return value would currently be + * ignored anyway.) - kw + */ +void LYExtSignal(int sig, + LYSigHandlerFunc_t *handler) +{ +#ifdef SIGWINCH + /* add more cases to if(condition) if required... */ + if (sig == SIGWINCH && LYNonRestartingSIGWINCH) { + struct sigaction act; + + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(sig, &act, NULL); + } else +#endif /* defined(SIGWINCH) */ + signal(sig, handler); +} +#endif /* HAVE_SIGACTION */ + +#if defined(SIGTSTP) && !defined(USE_SLANG) +#ifdef HAVE_SIGACTION +/* + * For switching a signal's handling between SIG_DFL and something (possibly) + * different that may have been set up by lynx code or e.g. by curses library. + * Uses sigaction to preserve / restore as much state as possible. + * + * Second arg is where to save or restore from. + * + * Third arg to_dfl specifies what to do: + * 1 Save current state in where, set handling to SIG_DFL + * 0 Restore current state to previously saved one in where + * + * Currently only used for SIGTSTP without SLANG, to prevent (n)curses signal + * handler from running while lynx is waiting in system() for an interactive + * command like an editor. - kw + */ +static BOOLEAN LYToggleSigDfl(int sig, + struct sigaction *where, + int to_dfl) +{ + int rv = -1; + struct sigaction oact; + + if (to_dfl == 1) { + rv = sigaction(sig, NULL, &oact); + if (rv == 0) { + if (oact.sa_handler != SIG_DFL) { + oact.sa_handler = SIG_DFL; + rv = sigaction(sig, &oact, where); + } else if (where) { + memcpy(where, &oact, sizeof(oact)); + rv = 0; + } + } + } else { + rv = sigaction(sig, where, NULL); + } + if (rv != 0) { + CTRACE((tfp, "Error in LYToggleSigDfl: %s\n", LYStrerror(errno))); + return FALSE; + } else + return TRUE; +} +#endif /* HAVE_SIGACTION */ +#endif /* SIGTSTP && !USE_SLANG */ + +/************** + * This bit of code catches window size change signals + */ + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +/* For systems that have both, but both can't be included, duh (or neither) */ +/* FIXME: this whole chunk may be redundant */ +#ifdef TERMIO_AND_CURSES +# ifdef TERMIO_AND_TERMIOS +# include <termio.h> +# else +# ifdef HAVE_TERMIOS_H +# include <termios.h> +# else +# ifdef HAVE_TERMIO_H +# include <termio.h> +# endif /* HAVE_TERMIO_H */ +# endif /* HAVE_TERMIOS_H */ +# endif /* TERMIO_AND_TERMIOS */ +#endif /* TERMIO_AND_CURSES */ + +void LYGetScreenSize(int sig GCC_UNUSED) +{ + int old_lines = LYlines; + int old_cols = LYcols; + +#ifdef USE_SLANG +#if defined(VMS) || defined(UNIX) + SLtt_get_screen_size(); +#endif /* VMS || UNIX */ + LYlines = SLtt_Screen_Rows; + LYcols = SLtt_Screen_Cols; + if (sig == 0) + /* If called from start_curses(), no need to record size-changed */ + return; +#else /* Curses: */ +#ifdef HAVE_SIZECHANGE +#if defined(TIOCGSIZE) + struct ttysize win; + + if (ioctl(0, TIOCGSIZE, &win) == 0) { + if (win.ts_lines != 0) { + LYlines = win.ts_lines; + } + if (win.ts_cols != 0) { + LYcols = win.ts_cols; + } + } +#elif defined(TIOCGWINSZ) + struct winsize win; + + if (ioctl(0, (long) TIOCGWINSZ, &win) == 0) { + if (win.ws_row != 0) { + LYlines = win.ws_row; + } + if (win.ws_col != 0) { + LYcols = win.ws_col; + } + } +#elif !defined(PDCURSES) +#error inconsistent settings for TIOCGSIZE/TIOCGWINSZ +#endif /* TIOCGSIZE/TIOCGWINSZ */ +#endif /* HAVE_SIZECHANGE */ + +#ifdef __EMX__ + { + int scrsize[2]; + + _scrsize(scrsize); + LYcols = scrsize[0]; + LYlines = scrsize[1]; + } +#endif + + if (LYlines <= 0) + LYlines = DFT_ROWS; + if (LYcols <= 0) + LYcols = DFT_COLS; +#endif /* USE_SLANG */ + + /* + * Check if the screen size has actually changed. - AJL + */ + if (LYlines != old_lines || LYcols != old_cols) { + recent_sizechange = TRUE; + CTRACE((tfp, "Window size changed from (%d,%d) to (%d,%d)\n", + old_lines, old_cols, LYlines, LYcols)); +#if defined(CAN_SWITCH_DISPLAY_CHARSET) && defined(CAN_AUTODETECT_DISPLAY_CHARSET) + /* May need to reload the font due to different char-box size */ + if (current_char_set != auto_display_charset) + Switch_Display_Charset(current_char_set, SWITCH_DISPLAY_CHARSET_RESIZE); +#endif + } +} + +/* + * curses/slang functions call this to start catching SIGWINCH when sig==0, + * and continue catching when sig!=0. + */ +void size_change(int sig GCC_UNUSED) +{ +#ifdef SIGWINCH + if (sig) + size_is_changed = TRUE; + LYExtSignal(SIGWINCH, size_change); +#endif /* SIGWINCH */ +} + +/* + * Utility for freeing the list of previous suggested filenames. - FM + */ +void HTSugFilenames_free(void) +{ + LYFreeStringList(sug_filenames); + sug_filenames = NULL; +} + +/* + * Utility for listing suggested filenames, making any repeated filenames the + * most current in the list. - FM + */ +void HTAddSugFilename(char *fname) +{ + char *tmp = NULL; + char *old; + HTList *cur; + + if (!non_empty(fname)) + return; + + StrAllocCopy(tmp, fname); + + if (!sug_filenames) { + sug_filenames = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTSugFilenames_free); +#endif + HTList_addObject(sug_filenames, tmp); + return; + } + + cur = sug_filenames; + while (NULL != (old = (char *) HTList_nextObject(cur))) { + if (!strcmp(old, tmp)) { + HTList_removeObject(sug_filenames, old); + FREE(old); + break; + } + } + HTList_addObject(sug_filenames, tmp); + + return; +} + +/* + * CHANGE_SUG_FILENAME -- Foteos Macrides 29-Dec-1993 Upgraded for use with + * Lynx2.2 - FM 17-Jan-1994 + */ +void change_sug_filename(char *fname) +{ + const char *cp2; + char *temp = 0, *cp, *cp1, *end; + +#ifdef VMS + char *dot; + int j, k; +#endif /* VMS */ + + /* + * Establish the current end of fname. + */ + end = fname + strlen(fname); + + /* + * Unescape fname. + */ + HTUnEscape(fname); + + /* + * Rename any temporary files. + */ + cp2 = wwwName(lynx_temp_space); + if (LYIsHtmlSep(*cp2)) { + HTSprintf0(&temp, "file://localhost%s" PID_FMT, cp2, GETPID()); + } else { + HTSprintf0(&temp, "file://localhost/%s" PID_FMT, cp2, GETPID()); + } + if (!StrNCmp(fname, temp, strlen(temp))) { + if ((cp = strrchr(fname, '.')) != 0) { + if (strlen(cp) > (strlen(temp) - 4)) + cp = NULL; + } + StrAllocCopy(temp, NonNull(cp)); + sprintf(fname, "temp%.*s", LY_MAXPATH - 10, temp); + } + FREE(temp); + + if (fname[strlen(fname) - 1] == '/') + /* + * Hmm... we have a directory name. It is annoying to see a + * scheme+host+path name as a suggested one, let's remove the + * last_slash and go ahead like we have a file name. - LP + */ + fname[strlen(fname) - 1] = '\0'; + + /* + * Remove everything up the the last_slash if there is one. + */ + if ((cp = strrchr(fname, '/')) != NULL && strlen(cp) > 1) { + cp1 = fname; + /* + * Go past the slash. + */ + cp++; + for (; *cp != '\0'; cp++, cp1++) { + *cp1 = *cp; + } + *cp1 = '\0'; + } +#ifdef _WINDOWS /* 1998/05/05 (Tue) 10:08:05 */ + if ((cp = strrchr(fname, '=')) != NULL && strlen(cp) > 1) { + cp1 = fname; + /* + * Go past the '='. + */ + cp++; + for (; *cp != '\0'; cp++, cp1++) { + *cp1 = *cp; + } + *cp1 = '\0'; + } +#endif + + /* + * Trim off date-size suffix, if present. + */ + if ((*(end - 1) == ']') && ((cp = strrchr(fname, '[')) != NULL) && + (cp > fname) && *(--cp) == ' ') { + while (*cp == ' ') { + *(cp--) = '\0'; + } + } +#ifdef VMS + /* + * Trim off VMS device and/or directory specs, if present. + */ + if ((cp = StrChr(fname, '[')) != NULL && + (cp1 = strrchr(cp, ']')) != NULL && strlen(cp1) > 1) { + cp1++; + for (cp = fname; *cp1 != '\0'; cp1++) { + *(cp++) = *cp1; + } + *cp = '\0'; + } + /* + * Replace illegal or problem characters. + */ + dot = fname + strlen(fname); + for (cp = fname; cp < dot; cp++) { + /* + * Replace with underscores. + */ + if (*cp == ' ' || *cp == '/' || *cp == ':' || + *cp == '[' || *cp == ']' || *cp == '&') { + *cp = '_'; + /* + * Replace with dashes. + */ + } else if (*cp == '!' || *cp == '?' || *cp == '\'' || + *cp == ',' || *cp == ':' || *cp == '"' || + *cp == '+' || *cp == '@' || *cp == '\\' || + *cp == '(' || *cp == ')' || *cp == '=' || + *cp == '<' || *cp == '>' || *cp == '#' || + *cp == '%' || *cp == '*' || *cp == '`' || + *cp == '~' || *cp == '^' || *cp == '|' || + *cp < ' ' || (UCH(*cp)) > 126) { + *cp = '-'; + } + } + + /* + * Collapse any serial underscores. + */ + cp = fname + 1; + j = 0; + while (cp < dot) { + if (fname[j] == '_' && *cp == '_') { + cp++; + } else { + fname[++j] = *cp++; + } + } + fname[++j] = '\0'; + + /* + * Collapse any serial dashes. + */ + dot = fname + (strlen(fname)); + cp = fname + 1; + j = 0; + while (cp < dot) { + if (fname[j] == '-' && *cp == '-') { + cp++; + } else { + fname[++j] = *cp++; + } + } + fname[++j] = '\0'; + + /* + * Trim any trailing or leading underscores or dashes. + */ + cp = fname + (strlen(fname)) - 1; + while (*cp == '_' || *cp == '-') { + *cp-- = '\0'; + } + if (fname[0] == '_' || fname[0] == '-') { + dot = fname + (strlen(fname)); + cp = fname; + while ((*cp == '_' || *cp == '-') && cp < dot) { + cp++; + } + j = 0; + while (cp < dot) { + fname[j++] = *cp++; + } + fname[j] = '\0'; + } + + /* + * Replace all but the last period with _'s, or second to last if last is + * followed by a terminal Z or z, or GZ or gz, + * e.g., convert foo.tar.Z to foo.tar_Z + * or, convert foo.tar.gz to foo.tar-gz + */ + j = strlen(fname) - 1; + if ((dot = strrchr(fname, '.')) != NULL) { + if (TOUPPER(fname[j]) == 'Z') { + if ((fname[j - 1] == '.') && + (((cp = StrChr(fname, '.')) != NULL) && cp < dot)) { + *dot = '_'; + dot = strrchr(fname, '.'); + } else if (((TOUPPER(fname[j - 1]) == 'G') && + fname[j - 2] == '.') && + (((cp = StrChr(fname, '.')) != NULL) && cp < dot)) { + *dot = '-'; + dot = strrchr(fname, '.'); + } + } + cp = fname; + while ((cp = StrChr(cp, '.')) != NULL && cp < dot) { + *cp = '_'; + } + + /* + * But if the root is > 39 characters, move the period appropriately to + * the left. + */ + while (dot - fname > 39) { + *dot = '\0'; + if ((cp = strrchr(fname, '_')) != NULL) { + *cp = '.'; + *dot = '_'; + } else if ((cp = strrchr(fname, '-')) != NULL) { + *cp = '.'; + *dot = '_'; + } else if (*(dot + 1) == '\0') { + j = strlen(fname); + while (j > 39) { + fname[j] = fname[j - 1]; + j--; + } + fname[j] = '.'; + } else { + *dot = '.'; + j = 39; + k = 0; + while (dot[k] != '\0') { + fname[j++] = dot[k++]; + } + fname[j] = '\0'; + } + dot = strrchr(fname, '.'); + } + + /* + * Make sure the extension is < 40 characters. + */ + if ((fname + strlen(fname) - dot) > 39) { + *(dot + 40) = '\0'; + } + + /* + * Trim trailing dashes or underscores. + */ + j = (strlen(fname) - 1); + while (fname[j] == '_' || fname[j] == '-') { + fname[j--] = '\0'; + } + } else { + /* + * No period, so put one on the end, or after the 39th character, + * trimming trailing dashes or underscores. + */ + if (strlen(fname) > 39) { + fname[39] = '\0'; + } + j = (strlen(fname) - 1); + while ((fname[j] == '_') || (fname[j] == '-')) { + j--; + } + fname[++j] = '.'; + fname[++j] = '\0'; + } + +#else /* Not VMS (UNIX): */ + + /* + * Replace problem characters. + */ + for (cp = fname; *cp != '\0'; cp++) { + switch (*cp) { + case '\'': + case '"': + case '/': + case ' ': + *cp = '-'; + } + } +#endif /* VMS (UNIX) */ + + /* + * Make sure the rest of the original string in nulled. + */ + cp = fname + strlen(fname); + while (cp < end) { + *cp++ = '\0'; + } + + return; +} + +/* + * Construct a temporary-filename. Assumes result is LY_MAXPATH chars long. + */ +static int fmt_tempname(char *result, + const char *prefix, + const char *suffix) +{ + int code; + +#ifdef HAVE_RAND_TEMPNAME +#define SIZE_TEMPNAME ((MAX_TEMPNAME / BITS_PER_CHAR) + 1) + static BOOL first = TRUE; + static int names_used = 0; + static unsigned char used_tempname[SIZE_TEMPNAME]; + unsigned offset, mask; +#endif + static unsigned counter; + char leaf[LY_MAXPATH]; + + if (prefix == 0) + prefix = ""; + if (suffix == 0) + suffix = ""; + /* + * Prefer a random value rather than a counter. + */ +#ifdef HAVE_RAND_TEMPNAME + if (first) { + lynx_srand((unsigned) ((long) time((time_t *) NULL) + (long) result)); + first = FALSE; + } + + /* We don't really need all of the bits from rand(). The high-order bits + * are the more-random portion in any case, but limiting the width of the + * generated name is done partly to avoid problems on systems that may not + * support long filenames. + */ + counter = MAX_TEMPNAME; + if (names_used < MAX_TEMPNAME) { + long get_rand = (long) lynx_rand(); + long max_rand = (long) LYNX_RAND_MAX; + + counter = (unsigned) (((float) MAX_TEMPNAME * (float) get_rand) / + (float) max_rand + 1); + /* + * Avoid reusing a temporary name, since there are places in the code + * which can refer to a temporary filename even after it has been + * closed and removed from the filesystem. + */ + do { + counter %= MAX_TEMPNAME; + offset = counter / BITS_PER_CHAR; + mask = (unsigned) (1 << (counter % BITS_PER_CHAR)); + if ((used_tempname[offset] & mask) == 0) { + names_used++; + used_tempname[offset] |= UCH(mask); + break; + } + } while ((used_tempname[offset] & mask) == 0); + } + if (names_used >= MAX_TEMPNAME) + HTAlert(gettext("Too many tempfiles")); +#else + counter++; +#endif + +#ifdef FNAMES_8_3 + /* + * The 'lynx_temp_space' string ends with a '/' or '\\', so we only have to + * limit the length of the leaf. As received (e.g., from HTCompressed), + * the suffix may contain more than a ".htm", e.g., "-txt.gz", so we trim + * off from the filename portion to make room. + */ + sprintf(leaf, PID_FMT PID_FMT, counter, GETPID()); + if (strlen(leaf) > 8) + leaf[8] = 0; + if (strlen(suffix) > 4 || *suffix != '.') { + const char *tail = StrChr(suffix, '.'); + + if (tail == 0) + tail = suffix + strlen(suffix); + if (8 - (tail - suffix) >= 0) + leaf[8 - (tail - suffix)] = 0; + } + strcat(leaf, suffix); +#else + sprintf(leaf, "L" PID_FMT "-%uTMP%s", GETPID(), counter, suffix); +#endif + /* + * Someone could have configured the temporary pathname to be too long. + */ + if ((strlen(prefix) + strlen(leaf)) < LY_MAXPATH) { + sprintf(result, "%s%s", prefix, leaf); + code = TRUE; + } else { + sprintf(result, "%.*s", LY_MAXPATH - 1, leaf); + code = FALSE; + } + CTRACE((tfp, "-> '%s'\n", result)); + return (code); +} + +/* + * Convert 4, 6, 2, 8 to left, right, down, up, etc. + */ +int number2arrows(int number) +{ + switch (number) { + case '1': + number = END_KEY; + break; + case '2': + number = DNARROW_KEY; + break; + case '3': + number = PGDOWN_KEY; + break; + case '4': + number = LTARROW_KEY; + break; + case '5': + number = DO_NOTHING; + break; + case '6': + number = RTARROW_KEY; + break; + case '7': + number = HOME_KEY; + break; + case '8': + number = UPARROW_KEY; + break; + case '9': + number = PGUP_KEY; + break; + } + + return (number); +} + +/* + * parse_restrictions takes a string of comma-separated restrictions and sets + * the corresponding flags to restrict the facilities available. + */ +/* The first two are special: we want to record whether "default" or "all" + * restrictions were applied, in addition to the detailed effects of those + * options. - kw + */ +/* skip the special flags when processing "all" and "default": */ +#define N_SPECIAL_RESTRICT_OPTIONS 2 +/* *INDENT-OFF* */ +static const struct { + const char *name; + BOOLEAN *flag; + BOOLEAN can; +} restrictions[] = { + { "default", &had_restrictions_default, TRUE }, + { "all", &had_restrictions_all, TRUE }, + { "inside_telnet", &no_inside_telnet, CAN_ANONYMOUS_INSIDE_DOMAIN_TELNET }, + { "outside_telnet", &no_outside_telnet, CAN_ANONYMOUS_OUTSIDE_DOMAIN_TELNET }, + { "telnet_port", &no_telnet_port, CAN_ANONYMOUS_GOTO_TELNET_PORT }, + { "inside_ftp", &no_inside_ftp, CAN_ANONYMOUS_INSIDE_DOMAIN_FTP }, + { "outside_ftp", &no_outside_ftp, CAN_ANONYMOUS_OUTSIDE_DOMAIN_FTP }, + { "inside_rlogin", &no_inside_rlogin, CAN_ANONYMOUS_INSIDE_DOMAIN_RLOGIN }, + { "outside_rlogin", &no_outside_rlogin, CAN_ANONYMOUS_OUTSIDE_DOMAIN_RLOGIN }, + { "suspend", &no_suspend, FALSE }, + { "editor", &no_editor, FALSE }, + { "shell", &no_shell, FALSE }, + { "bookmark", &no_bookmark, FALSE }, + { "multibook", &no_multibook, FALSE }, + { "bookmark_exec", &no_bookmark_exec, FALSE }, + { "option_save", &no_option_save, FALSE }, + { "print", &no_print, CAN_ANONYMOUS_PRINT }, + { "download", &no_download, FALSE }, + { "disk_save", &no_disk_save, FALSE }, +#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) + { "exec", &no_exec, LOCAL_EXECUTION_LINKS_ALWAYS_OFF_FOR_ANONYMOUS }, +#endif + { "lynxcgi", &no_lynxcgi, FALSE }, + { "exec_frozen", &exec_frozen, FALSE }, + { "goto", &no_goto, CAN_ANONYMOUS_GOTO }, + { "jump", &no_jump, CAN_ANONYMOUS_JUMP }, + { "file_url", &no_file_url, FALSE }, +#ifndef DISABLE_NEWS + { "news_post", &no_newspost, FALSE }, + { "inside_news", &no_inside_news, CAN_ANONYMOUS_INSIDE_DOMAIN_READ_NEWS }, + { "outside_news", &no_outside_news, CAN_ANONYMOUS_OUTSIDE_DOMAIN_READ_NEWS }, +#endif + { "mail", &no_mail, CAN_ANONYMOUS_MAIL }, + { "dotfiles", &no_dotfiles, FALSE }, + { "useragent", &no_useragent, FALSE }, +#ifdef SUPPORT_CHDIR + { "chdir", &no_chdir, FALSE }, +#endif +#ifdef DIRED_SUPPORT + { "dired_support", &no_dired_support, FALSE }, +#ifdef OK_PERMIT + { "change_exec_perms", &no_change_exec_perms, FALSE }, +#endif /* OK_PERMIT */ +#endif /* DIRED_SUPPORT */ +#ifdef USE_EXTERNALS + { "externals", &no_externals, FALSE }, +#endif + { "lynxcfg_info", &no_lynxcfg_info, CAN_ANONYMOUS_VIEW_LYNXCFG_INFO }, +#ifndef NO_CONFIG_INFO + { "lynxcfg_xinfo", &no_lynxcfg_xinfo, CAN_ANONYMOUS_VIEW_LYNXCFG_EXTENDED_INFO }, +#ifdef HAVE_CONFIG_H + { "compileopts_info", &no_compileopts_info, CAN_ANONYMOUS_VIEW_COMPILEOPTS_INFO }, +#endif +#endif + /* put "goto" restrictions on the end, since they are a refinement */ +#ifndef DISABLE_BIBP + { "goto_bibp", &no_goto_bibp, CAN_ANONYMOUS_GOTO_BIBP }, +#endif +#ifdef HAVE_CONFIG_H +#ifndef NO_CONFIG_INFO + { "goto_configinfo", &no_goto_configinfo, CAN_ANONYMOUS_GOTO_CONFIGINFO }, +#endif +#endif + { "goto_cso", &no_goto_cso, CAN_ANONYMOUS_GOTO_CSO }, + { "goto_file", &no_goto_file, CAN_ANONYMOUS_GOTO_FILE }, +#ifndef DISABLE_FINGER + { "goto_finger", &no_goto_finger, CAN_ANONYMOUS_GOTO_FINGER }, +#endif + { "goto_ftp", &no_goto_ftp, CAN_ANONYMOUS_GOTO_FTP }, +#ifndef DISABLE_GOPHER + { "goto_gopher", &no_goto_gopher, CAN_ANONYMOUS_GOTO_GOPHER }, +#endif + { "goto_http", &no_goto_http, CAN_ANONYMOUS_GOTO_HTTP }, + { "goto_https", &no_goto_https, CAN_ANONYMOUS_GOTO_HTTPS }, + { "goto_lynxcgi", &no_goto_lynxcgi, CAN_ANONYMOUS_GOTO_LYNXCGI }, + { "goto_lynxexec", &no_goto_lynxexec, CAN_ANONYMOUS_GOTO_LYNXEXEC }, + { "goto_lynxprog", &no_goto_lynxprog, CAN_ANONYMOUS_GOTO_LYNXPROG }, + { "goto_mailto", &no_goto_mailto, CAN_ANONYMOUS_GOTO_MAILTO }, +#ifndef DISABLE_NEWS + { "goto_news", &no_goto_news, CAN_ANONYMOUS_GOTO_NEWS }, + { "goto_nntp", &no_goto_nntp, CAN_ANONYMOUS_GOTO_NNTP }, +#endif + { "goto_rlogin", &no_goto_rlogin, CAN_ANONYMOUS_GOTO_RLOGIN }, +#ifndef DISABLE_NEWS + { "goto_snews", &no_goto_snews, CAN_ANONYMOUS_GOTO_SNEWS }, +#endif + { "goto_telnet", &no_goto_telnet, CAN_ANONYMOUS_GOTO_TELNET }, + { "goto_tn3270", &no_goto_tn3270, CAN_ANONYMOUS_GOTO_TN3270 }, + { "goto_wais", &no_goto_wais, CAN_ANONYMOUS_GOTO_WAIS }, +}; +/* *INDENT-ON* */ + +/* This will make no difference between '-' and '_'. It does only in/equality + * compare. It assumes that p2 can't contain dashes, but p1 can. This + * function is also used (if macro OPTNAME_ALLOW_DASHES doesn't have value of + * zero) for compare of commandline options -VH + */ +BOOL strn_dash_equ(const char *p1, + const char *p2, + int len) +{ + while (len--) { + if (!*p2) + return 0; /* canonical name is shorter */ + switch (*p1) { + case 0: + return 0; + case '-': + case '_': + if (*p2 != '_') + return 0; + else + break; + default: + if (*p1 != *p2) + return 0; + } + ++p1; + ++p2; + } + return 1; +} + +/* Uncomment following lines to allow only exact string matching */ +/* #define RESTRICT_NM_ALLOW_DASHES 0 */ + +#ifndef RESTRICT_NM_ALLOW_DASHES +# define RESTRICT_NM_ALLOW_DASHES 1 +#endif + +#if RESTRICT_NM_ALLOW_DASHES +# define RESTRICT_NM_EQU(a,b,len) strn_dash_equ(a,b,len) +#else +# define RESTRICT_NM_EQU(a,b,len) STRNEQ(a,b,len) +#endif + +/* + * Returns the inx'th name from the restrictions table, or null if inx is + * out of range. + */ +const char *index_to_restriction(unsigned inx) +{ + if (inx < TABLESIZE(restrictions)) + return restrictions[inx].name; + return NULL; +} + +/* + * Returns the value TRUE/FALSE of a given restriction, or -1 if it is not + * one that we recognize. + */ +int find_restriction(const char *name, + int len) +{ + unsigned i; + + if (len < 0) + len = (int) strlen(name); + for (i = 0; i < TABLESIZE(restrictions); i++) { + if (RESTRICT_NM_EQU(name, restrictions[i].name, len)) { + return (*restrictions[i].flag); + } + } + return -1; +} + +void parse_restrictions(const char *s) +{ + const char *p; + const char *word; + unsigned i; + BOOLEAN found; + + p = s; + while (*p) { + p = LYSkipCBlanks(p); + if (*p == '\0') + break; + word = p; + while (*p != ',' && *p != '\0') + p++; + + found = FALSE; + if (RESTRICT_NM_EQU(word, "all", (int) (p - word))) { + found = TRUE; + for (i = N_SPECIAL_RESTRICT_OPTIONS; + i < TABLESIZE(restrictions); + i++) + *(restrictions[i].flag) = TRUE; + } else if (RESTRICT_NM_EQU(word, "default", (int) (p - word))) { + found = TRUE; + for (i = N_SPECIAL_RESTRICT_OPTIONS; + i < TABLESIZE(restrictions); + i++) + *(restrictions[i].flag) = (BOOLEAN) !restrictions[i].can; + } else { + for (i = 0; i < TABLESIZE(restrictions); i++) { + if (RESTRICT_NM_EQU(word, restrictions[i].name, (int) (p - word))) { + *(restrictions[i].flag) = TRUE; + found = TRUE; + break; + } + } + } + if (!found) { + printf("%s: %.*s\n", gettext("unknown restriction"), + (int) (p - word), word); + exit_immediately(EXIT_FAILURE); + } + if (*p) + p++; + } + + /* + * If shell is restricted, set restrictions on related topics. + */ + if (no_shell) { + no_goto_lynxexec = TRUE; + no_goto_lynxprog = TRUE; + no_goto_lynxcgi = TRUE; +#ifdef EXEC_LINKS + local_exec_on_local_files = TRUE; +#endif + } +} + +void print_restrictions_to_fd(FILE *fp) +{ + unsigned i, count = 0; + + for (i = 0; i < TABLESIZE(restrictions); i++) { + if (*(restrictions[i].flag) == TRUE) { + count++; + } + } + if (!count) { + fprintf(fp, gettext("No restrictions set.\n")); + return; + } + fprintf(fp, gettext("Restrictions set:\n")); + for (i = 0; i < TABLESIZE(restrictions); i++) { + if (*(restrictions[i].flag) == TRUE) { + /* if "goto" is restricted, don't bother tell about its + * refinements + */ + if (StrNCmp(restrictions[i].name, "goto_", 5) + || !no_goto) + fprintf(fp, " %s\n", restrictions[i].name); + } + } +} + +#ifdef VMS +#include <jpidef.h> +#include <maildef.h> +#include <starlet.h> + +typedef struct _VMSMailItemList { + short buffer_length; + short item_code; + void *buffer_address; + long *return_length_address; +} VMSMailItemList; + +void LYCheckMail(void) +{ + static BOOL firsttime = TRUE, failure = FALSE; + static char user[13], dir[252]; + static long userlen = 0, dirlen; + static time_t lastcheck = 0; + time_t now; + static short new, lastcount; + long ucontext = 0, status; + short flags = MAIL$M_NEWMSG; + /* *INDENT-OFF* */ + VMSMailItemList + null_list[] = {{0,0,0,0}}, + jpi_list[] = {{sizeof(user) - 1,JPI$_USERNAME,(void *)user,&userlen}, + {0,0,0,0}}, + uilist[] = {{0,MAIL$_USER_USERNAME,0,0}, + {0,0,0,0}}, + uolist[] = {{sizeof(new),MAIL$_USER_NEW_MESSAGES,&new,0}, + {sizeof(dir),MAIL$_USER_FULL_DIRECTORY,dir,&dirlen}, + {0,0,0,0}}; + /* *INDENT-ON* */ + + extern long mail$user_begin(); + extern long mail$user_get_info(); + extern long mail$user_end(); + + if (failure) + return; + + if (firsttime) { + firsttime = FALSE; + /* Get the username. */ + status = sys$getjpiw(0, 0, 0, jpi_list, 0, 0, 0); + if (!(status & 1)) { + failure = TRUE; + return; + } + user[userlen] = '\0'; + LYTrimTrailing(user); + } + + /* Minimum report interval is 60 sec. */ + time(&now); + if (now - lastcheck < 60) + return; + lastcheck = now; + + /* Get the current newmail count. */ + status = mail$user_begin(&ucontext, null_list, null_list); + if (!(status & 1)) { + failure = TRUE; + return; + } + uilist[0].buffer_length = strlen(user); + uilist[0].buffer_address = user; + status = mail$user_get_info(&ucontext, uilist, uolist); + if (!(status & 1)) { + failure = TRUE; + return; + } + + /* Should we report anything to the user? */ + if (new > 0) { + if (lastcount == 0) + /* Have newmail at startup of Lynx. */ + HTUserMsg(HAVE_UNREAD_MAIL_MSG); + else if (new > lastcount) + /* Have additional mail since last report. */ + HTUserMsg(HAVE_NEW_MAIL_MSG); + lastcount = new; + return; + } + lastcount = new; + + /* Clear the context */ + mail$user_end((long *) &ucontext, null_list, null_list); + return; +} +#else +void LYCheckMail(void) +{ + static BOOL firsttime = TRUE; + static char *mf; + static time_t lastcheck; + static time_t lasttime; + static long lastsize; + time_t now; + struct stat st; + + if (firsttime) { + mf = LYGetEnv("MAIL"); + firsttime = FALSE; + time(&lasttime); + } + + if (mf == NULL) + return; + + time(&now); + if (now - lastcheck < 60) + return; + lastcheck = now; + + if ((stat(mf, &st) < 0) + || !S_ISREG(st.st_mode)) { + mf = NULL; + return; + } + + if (st.st_size > 0) { + if (((lasttime != st.st_mtime) && (st.st_mtime > st.st_atime)) + || ((lastsize != 0) && (st.st_size > lastsize))) + HTUserMsg(HAVE_NEW_MAIL_MSG); + else if (lastsize == 0) + HTUserMsg(HAVE_MAIL_MSG); + } + lastsize = (long) st.st_size; + lasttime = st.st_mtime; + return; +} +#endif /* VMS */ + +/* + * This function ensures that an href will be + * converted to a fully resolved, absolute URL, + * with guessing of the host or expansions of + * lead tildes via LYConvertToURL() if needed, + * and tweaking/simplifying via HTParse(). It + * is used for LynxHome, startfile, homepage, + * and 'g'oto entries, after they have been + * passed to LYFillLocalFileURL(). - FM + * Such URLs have no `base' reference to which they + * could be resolved. LYLegitimizeHREF could not be used. + */ +void LYEnsureAbsoluteURL(char **href, + const char *name, + int fixit) +{ + char *temp = NULL; + + if (isEmpty(*href)) + return; + + /* + * Check whether to fill in localhost. - FM + */ + LYFillLocalFileURL(href, "file://localhost"); + + /* + * If it is not a URL then make it one. + */ + if (!strcasecomp(*href, STR_NEWS_URL)) { + StrAllocCat(*href, "*"); + } else if (!strcasecomp(*href, STR_SNEWS_URL)) { + StrAllocCat(*href, "/*"); + } + + if (!is_url(*href)) { + CTRACE((tfp, "%s%s'%s' is not a URL\n", + NonNull(name), (name ? " " : ""), *href)); + LYConvertToURL(href, fixit); + } + + temp = HTParse(*href, "", PARSE_ALL); + if (non_empty(temp)) + StrAllocCopy(*href, temp); + FREE(temp); +} + +static const char *default_scheme = "http://"; + +static const char *guess_scheme(const char *url) +{ + const char *scheme = NULL; + + if (LYGuessScheme && non_empty(url)) { + if (0 == strncasecomp(url, "www.", 4)) { + scheme = "http://"; + } else if (0 == strncasecomp(url, "ftp.", 4)) { + scheme = "ftp://"; + } else if (0 == strncasecomp(url, "gopher.", 7)) { + scheme = "gopher://"; + } else if (0 == strncasecomp(url, "wais.", 5)) { + scheme = "wais://"; + } else if (0 == strncasecomp(url, "cso.", 4) || + 0 == strncasecomp(url, "ns.", 3) || + 0 == strncasecomp(url, "ph.", 3)) { + scheme = "cso://"; + } else if (0 == strncasecomp(url, "finger.", 7)) { + scheme = "finger://"; + } else if (0 == strncasecomp(url, "news.", 5)) { + scheme = "news://"; + } else if (0 == strncasecomp(url, "nntp.", 5)) { + scheme = "nntp://"; + } + } + CTRACE((tfp, "guess_scheme(%s) -> '%s'\n", NonNull(url), NonNull(scheme))); + return scheme; +} + +/* + * This function rewrites and reallocates a previously allocated string that + * begins with an Internet host name so that the string begins with its guess + * of the scheme based on the first field of the host name, or the default + * scheme if no guess was made. + */ +static void LYAddSchemeForURL(char **AllocatedString) +{ + if (non_empty(*AllocatedString)) { + char *Str = NULL; + const char *scheme = guess_scheme(*AllocatedString); + + if (scheme == NULL) + scheme = default_scheme; + + StrAllocCopy(Str, scheme); + StrAllocCat(Str, *AllocatedString); + StrAllocCopy(*AllocatedString, Str); + + FREE(Str); + } +} + +/* + * This function rewrites and reallocates a previously allocated string so that + * the first element is a confirmed Internet host, and returns TRUE, otherwise + * it does not modify the string and returns FALSE. + * + * It first tries the element as is, then, if the element does not end with a + * dot, it adds prefixes from the (comma separated) prefix list argument, and, + * if the element does not begin with a dot, suffixes from the (comma + * separated) suffix list arguments (e.g., www.host.com, then www.host,edu, + * then www.host.net, then www.host.org). + * + * The remaining path, if one is present, will be appended to the expanded + * host. + * + * It also takes into account whether a colon is in the element or suffix, and + * includes that and what follows as a port field for the expanded host field + * (e.g, wfbr:8002/dir/lynx should yield www.wfbr.edu:8002/dir/lynx). + */ +static BOOLEAN LYExpandHostForURL(char **AllocatedString, + char *prefix_list, + char *suffix_list) +{ + char *DomainPrefix = NULL; + const char *StartP, *EndP; + char *DomainSuffix = NULL; + const char *StartS, *EndS; + char *Str = NULL, *StrColon = NULL, *MsgStr = NULL; + char *Host = NULL, *HostColon = NULL, *host = NULL; + char *Path = NULL; + char *Fragment = NULL; + BOOLEAN GotHost = FALSE; + BOOLEAN Startup = (BOOL) (helpfilepath == NULL); + + /* + * If it's a NULL or zero-length string, or if it begins with a slash or + * hash, don't continue pointlessly. - FM + */ + if (isEmpty(*AllocatedString) || + *AllocatedString[0] == '/' || + *AllocatedString[0] == '#') { + return GotHost; + } + + /* + * If it's a partial or relative path, don't continue pointlessly. - FM + */ + if (!StrNCmp(*AllocatedString, "..", 2) || + !StrNCmp(*AllocatedString, "./", 2)) { + return GotHost; + } + + /* + * Make a clean copy of the string, and trim off the path if one is + * present, but save the information so we can restore the path after + * filling in the Host[:port] field. - FM + */ + StrAllocCopy(Str, *AllocatedString); + if ((Path = StrChr(Str, '/')) != NULL) { + /* + * Have a path. Any fragment should already be included in Path. - FM + */ + *Path = '\0'; + } else { + /* + * No path, so check for a fragment and trim that, to be restored after + * filling in the Host[:port] field. - FM + */ + Fragment = trimPoundSelector(Str); + } + + /* + * If the potential host string has a colon, assume it begins a port field, + * and trim it off, but save the information so we can restore the port + * field after filling in the host field. - FM + */ + if ((StrColon = strrchr(Str, ':')) != NULL && + isdigit(UCH(StrColon[1])) && StrChr(StrColon, ']') == NULL) { + if (StrColon == Str) { + goto cleanup; + } + *StrColon = '\0'; + } + + /* + * Do a DNS test on the potential host field as presently trimmed. - FM + */ + StrAllocCopy(host, Str); + strip_userid(host, FALSE); + HTUnEscape(host); + if (LYCursesON) { + StrAllocCopy(MsgStr, WWW_FIND_MESSAGE); + StrAllocCat(MsgStr, host); + StrAllocCat(MsgStr, FIRST_SEGMENT); + HTProgress(MsgStr); + } else if (Startup && !dump_output_immediately) { + fprintf(stdout, "%s '%s'%s\r\n", WWW_FIND_MESSAGE, host, FIRST_SEGMENT); + } +#ifdef INET6 + if (HTCheckAddrInfo(host, 80)) +#else + if (LYCheckHostByName(host)) +#endif /* INET6 */ + { + /* + * Clear any residual interrupt. - FM + */ + if (LYCursesON && HTCheckForInterrupt()) { + CTRACE((tfp, + "LYExpandHostForURL: Ignoring interrupt because '%s' resolved.\n", + host)); + } + + /* + * Return success. - FM + */ + GotHost = TRUE; + goto cleanup; + } else if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) { + /* + * Give the user chance to interrupt lookup cycles. - KW & FM + */ + CTRACE((tfp, + "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n", + host)); + + /* + * Return failure. - FM + */ + goto cleanup; + } + + /* + * Set the first prefix, making it a zero-length string if the list is NULL + * or if the potential host field ends with a dot. - FM + */ + StartP = ((prefix_list && Str[strlen(Str) - 1] != '.') + ? prefix_list + : ""); + /* + * If we have a prefix, but the allocated string is one of the common host + * prefixes, make our prefix a zero-length string. - FM + */ + if (*StartP && *StartP != '.') { + if (guess_scheme(*AllocatedString) != NULL) { + StartP = ""; + } + } + while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) { + StartP++; /* Skip whitespace and separators */ + } + EndP = StartP; + while (*EndP && !WHITE(*EndP) && *EndP != ',') { + EndP++; /* Find separator */ + } + StrAllocCopy(DomainPrefix, StartP); + DomainPrefix[EndP - StartP] = '\0'; + + /* + * Test each prefix with each suffix. - FM + */ + do { + /* + * Set the first suffix, making it a zero-length string if the list is + * NULL or if the potential host field begins with a dot. - FM + */ + StartS = ((suffix_list && *Str != '.') + ? suffix_list + : ""); + while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) { + StartS++; /* Skip whitespace and separators */ + } + EndS = StartS; + while (*EndS && !WHITE(*EndS) && *EndS != ',') { + EndS++; /* Find separator */ + } + StrAllocCopy(DomainSuffix, StartS); + DomainSuffix[EndS - StartS] = '\0'; + + /* + * Create domain names and do DNS tests. - FM + */ + do { + StrAllocCopy(Host, DomainPrefix); + StrAllocCat(Host, ((*Str == '.') ? (Str + 1) : Str)); + if (Host[strlen(Host) - 1] == '.') { + Host[strlen(Host) - 1] = '\0'; + } + StrAllocCat(Host, DomainSuffix); + if ((HostColon = strrchr(Host, ':')) != NULL && + isdigit(UCH(HostColon[1]))) { + *HostColon = '\0'; + } + StrAllocCopy(host, Host); + HTUnEscape(host); + if (LYCursesON) { + StrAllocCopy(MsgStr, WWW_FIND_MESSAGE); + StrAllocCat(MsgStr, host); + StrAllocCat(MsgStr, GUESSING_SEGMENT); + HTProgress(MsgStr); + } else if (Startup && !dump_output_immediately) { + fprintf(stdout, "%s '%s'%s\n", WWW_FIND_MESSAGE, host, GUESSING_SEGMENT); + } + GotHost = LYCheckHostByName(host); + if (HostColon != NULL) { + *HostColon = ':'; + } + if (GotHost == FALSE) { + /* + * Give the user chance to interrupt lookup cycles. - KW + */ + if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) { + CTRACE((tfp, + "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n", + host)); + goto cleanup; /* We didn't find a valid name. */ + } + + /* + * Advance to the next suffix, or end of suffix list. - FM + */ + StartS = ((*EndS == '\0') ? EndS : (EndS + 1)); + while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) { + StartS++; /* Skip whitespace and separators */ + } + EndS = StartS; + while (*EndS && !WHITE(*EndS) && *EndS != ',') { + EndS++; /* Find separator */ + } + LYStrNCpy(DomainSuffix, StartS, (EndS - StartS)); + } + } while ((GotHost == FALSE) && (*DomainSuffix != '\0')); + + if (GotHost == FALSE) { + /* + * Advance to the next prefix, or end of prefix list. - FM + */ + StartP = ((*EndP == '\0') ? EndP : (EndP + 1)); + while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) { + StartP++; /* Skip whitespace and separators */ + } + EndP = StartP; + while (*EndP && !WHITE(*EndP) && *EndP != ',') { + EndP++; /* Find separator */ + } + LYStrNCpy(DomainPrefix, StartP, (EndP - StartP)); + } + } while ((GotHost == FALSE) && (*DomainPrefix != '\0')); + + /* + * If a test passed, restore the port field if we had one and there is no + * colon in the expanded host, and the path if we had one, and reallocate + * the original string with the expanded Host[:port] field included. - FM + */ + if (GotHost) { + if (StrColon && StrChr(Host, ':') == NULL) { + *StrColon = ':'; + StrAllocCat(Host, StrColon); + } + if (Path) { + *Path = '/'; + StrAllocCat(Host, Path); + } else if (Fragment) { + StrAllocCat(Host, "/"); + restorePoundSelector(Fragment); + StrAllocCat(Host, Fragment); + } + StrAllocCopy(*AllocatedString, Host); + } + + /* + * Clear any residual interrupt. - FM + */ + if (LYCursesON && HTCheckForInterrupt()) { + CTRACE((tfp, + "LYExpandHostForURL: Ignoring interrupt because '%s' %s.\n", + host, + (GotHost ? "resolved" : "timed out"))); + } + + /* + * Clean up and return the last test result. - FM + */ + cleanup: + FREE(DomainPrefix); + FREE(DomainSuffix); + FREE(Str); + FREE(MsgStr); + FREE(Host); + FREE(host); + return GotHost; +} +/* + * Rewrite and reallocate a previously allocated string as a file URL if the + * string resolves to a file or directory on the local system, otherwise as an + * http URL. - FM + */ +void LYConvertToURL(char **AllocatedString, + int fixit) +{ + char *old_string = *AllocatedString; + char *temp = NULL; + char *cp = NULL; + +#ifndef VMS + struct stat st; +#endif /* !VMS */ + + if (isEmpty(old_string)) + return; + +#if defined(USE_DOS_DRIVES) + { + char *cp_url = *AllocatedString; + + for (; *cp_url != '\0'; cp_url++) + if (*cp_url == '\\') + *cp_url = '/'; + cp_url--; + if (LYIsDosDrive(*AllocatedString) && *cp_url == ':') + LYAddPathSep(AllocatedString); + } +#endif /* USE_DOS_DRIVES */ + + *AllocatedString = NULL; /* so StrAllocCopy doesn't free it */ + StrAllocCopy(*AllocatedString, "file://localhost"); + + if (*old_string != '/') { + char *fragment = NULL; + +#if defined(USE_DOS_DRIVES) + StrAllocCat(*AllocatedString, "/"); +#endif /* USE_DOS_DRIVES */ +#ifdef VMS + /* + * Not a SHELL pathspec. Get the full VMS spec and convert it. + */ + char *cur_dir = NULL; + static char url_file[LY_MAXPATH], file_name[LY_MAXPATH], dir_name[LY_MAXPATH]; + unsigned long context = 0; + + $DESCRIPTOR(url_file_dsc, url_file); + $DESCRIPTOR(file_name_dsc, file_name); + if (LYIsTilde(*old_string)) { + /* + * On VMS, we'll accept '~' on the command line as Home_Dir(), and + * assume the rest of the path, if any, has SHELL syntax. + */ + StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir())); + if ((cp = StrChr(old_string, '/')) != NULL) { + /* + * Append rest of path, if present, skipping "user" if "~user" + * was entered, simplifying, and eliminating any residual + * relative elements. - FM + */ + StrAllocCopy(temp, cp); + LYTrimRelFromAbsPath(temp); + StrAllocCat(*AllocatedString, temp); + FREE(temp); + } + goto have_VMS_URL; + } else { + fragment = trimPoundSelector(old_string); + LYStrNCpy(url_file, old_string, sizeof(url_file) - 1); + } + url_file_dsc.dsc$w_length = (short) strlen(url_file); + if (1 & lib$find_file(&url_file_dsc, &file_name_dsc, &context, + 0, 0, 0, 0)) { + /* + * We found the file. Convert to a URL pathspec. + */ + if ((cp = StrChr(file_name, ';')) != NULL) { + *cp = '\0'; + } + LYLowerCase(file_name); + StrAllocCat(*AllocatedString, HTVMS_wwwName(file_name)); + if ((cp = StrChr(old_string, ';')) != NULL) { + StrAllocCat(*AllocatedString, cp); + } + if (fragment != NULL) { + restorePoundSelector(fragment); + StrAllocCat(*AllocatedString, fragment); + fragment = NULL; + } + } else if ((NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) && + 0 == chdir(old_string)) { + /* + * Probably a directory. Try converting that. + */ + StrAllocCopy(cur_dir, dir_name); + restorePoundSelector(fragment); + if (NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) { + /* + * Yup, we got it! + */ + LYLowerCase(dir_name); + StrAllocCat(*AllocatedString, dir_name); + if (fragment != NULL) { + StrAllocCat(*AllocatedString, fragment); + fragment = NULL; + } + } else { + /* + * Nope. Assume it's an http URL with the "http://" defaulted, + * if we can't rule out a bad VMS path. + */ + fragment = NULL; + if (StrChr(old_string, '[') || + ((cp = StrChr(old_string, ':')) != NULL && + !isdigit(UCH(cp[1]))) || + !LYExpandHostForURL(&old_string, + URLDomainPrefixes, + URLDomainSuffixes)) { + /* + * Probably a bad VMS path (but can't be sure). Use + * original pathspec for the error message that will + * result. + */ + sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string); + CTRACE((tfp, + "Can't find '%s' Will assume it's a bad path.\n", + old_string)); + StrAllocCat(*AllocatedString, url_file); + } else { + LYAddSchemeForURL(&old_string); + StrAllocCopy(*AllocatedString, old_string); + } + } + } else { + /* + * Nothing found. Assume it's an http URL with the "http://" + * defaulted, if we can't rule out a bad VMS path. + */ + restorePoundSelector(fragment); + fragment = NULL; + + if (StrChr(old_string, '[') || + ((cp = StrChr(old_string, ':')) != NULL && + !isdigit(UCH(cp[1]))) || + !LYExpandHostForURL(&old_string, + URLDomainPrefixes, + URLDomainSuffixes)) { + /* + * Probably a bad VMS path (but can't be sure). Use original + * pathspec for the error message that will result. + */ + sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string); + CTRACE((tfp, "Can't find '%s' Will assume it's a bad path.\n", + old_string)); + StrAllocCat(*AllocatedString, url_file); + } else { + LYAddSchemeForURL(&old_string); + StrAllocCopy(*AllocatedString, old_string); + } + } + lib$find_file_end(&context); + FREE(cur_dir); + have_VMS_URL: + CTRACE((tfp, "Trying: '%s'\n", *AllocatedString)); +#else /* not VMS: */ +#if defined(USE_DOS_DRIVES) +#ifdef _WINDOWS + if (*old_string == '.') { + char fullpath[MAX_PATH + 1]; + char *filepart = NULL; + DWORD chk; + + chk = GetFullPathNameA(old_string, MAX_PATH + 1, + fullpath, &filepart); + if (chk != 0) { + StrAllocCopy(temp, wwwName(fullpath)); + StrAllocCat(*AllocatedString, temp); + FREE(temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + } else { + StrAllocCat(*AllocatedString, old_string); + } + } +#else + if (strlen(old_string) == 1 && *old_string == '.') { + /* + * They want . + */ + char curdir[LY_MAXPATH]; + + StrAllocCopy(temp, wwwName(Current_Dir(curdir))); + StrAllocCat(*AllocatedString, temp); + FREE(temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + } +#endif + else +#endif /* USE_DOS_DRIVES */ + if (LYIsTilde(*old_string)) { + char *his_home = NULL; + + StrAllocCopy(his_home, old_string); + LYTildeExpand(&his_home, FALSE); + StrAllocCat(*AllocatedString, his_home); + FREE(his_home); + + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + } else { + /* + * Create a full path to the current default directory. + */ + char curdir[LY_MAXPATH]; + char *temp2 = NULL; + BOOL is_local = FALSE; + + Current_Dir(curdir); + /* + * Concatenate and simplify, trimming any residual relative + * elements. - FM + */ +#if defined (USE_DOS_DRIVES) + if (old_string[1] != ':' && old_string[1] != '|') { + StrAllocCopy(temp, wwwName(curdir)); + LYAddHtmlSep(&temp); + LYStrNCpy(curdir, temp, (sizeof(curdir) - 1)); + StrAllocCat(temp, old_string); + } else { + curdir[0] = '\0'; + /* 1998/01/13 (Tue) 12:24:33 */ + if (old_string[1] == '|') + old_string[1] = ':'; + StrAllocCopy(temp, old_string); + + if (strlen(temp) == 2 && LYIsDosDrive(temp)) + LYAddPathSep(&temp); + } +#else + StrAllocCopy(temp, curdir); + StrAllocCat(temp, "/"); + StrAllocCat(temp, old_string); +#endif /* USE_DOS_DRIVES */ + LYTrimRelFromAbsPath(temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp)); + if ((stat(temp, &st) > -1) || + LYCanReadFile(temp)) { + /* + * It is a subdirectory or file on the local system. + */ +#if defined (USE_DOS_DRIVES) + /* Don't want to see DOS local paths like c: escaped */ + /* especially when we really have file://localhost/ */ + /* at the beginning. To avoid any confusion we allow */ + /* escaping the path if URL specials % or # present. */ + if (StrChr(temp, '#') == NULL && StrChr(temp, '%') == NULL) + StrAllocCopy(cp, temp); + else + cp = HTEscape(temp, URL_PATH); +#else + cp = HTEscape(temp, URL_PATH); +#endif /* USE_DOS_DRIVES */ + StrAllocCat(*AllocatedString, cp); + FREE(cp); + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + is_local = TRUE; + } else { + char *cp2 = NULL; + + StrAllocCopy(temp2, curdir); + LYAddPathSep(&temp2); + StrAllocCopy(cp, old_string); + fragment = trimPoundSelector(cp); + HTUnEscape(cp); /* unescape given path without fragment */ + StrAllocCat(temp2, cp); /* append to current dir */ + StrAllocCopy(cp2, temp2); /* keep a copy in cp2 */ + LYTrimRelFromAbsPath(temp2); +#ifdef WIN_EX /* 1998/07/31 (Fri) 09:09:03 */ + HTUnEscape(temp2); /* for LFN */ +#endif + + if (strcmp(temp2, temp) != 0 && + ((stat(temp2, &st) > -1) || + LYCanReadFile(temp2))) { + /* + * It is a subdirectory or file on the local system with + * escaped characters and/or a fragment to be appended to + * the URL. - FM + */ + + FREE(temp); + if (strcmp(cp2, temp2) == 0) { + /* + * LYTrimRelFromAbsPath did nothing, use old_string as + * given. - kw + */ + temp = HTEscape(curdir, URL_PATH); + LYAddHtmlSep(&temp); + StrAllocCat(temp, old_string); + } else { + temp = HTEscape(temp2, URL_PATH); + if (fragment != NULL) { + restorePoundSelector(fragment); + StrAllocCat(temp, fragment); + } + } + StrAllocCat(*AllocatedString, temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + is_local = TRUE; + + } else if (StrChr(curdir, '#') != NULL || + StrChr(curdir, '%') != NULL) { + /* + * If PWD has some unusual characters, construct a filename + * in temp where those are escaped. This is mostly to + * prevent this function from returning with some weird URL + * if the LYExpandHostForURL tests further down fail. - kw + */ + FREE(temp); + if (strcmp(cp2, temp2) == 0) { + /* + * LYTrimRelFromAbsPath did nothing, use old_string as + * given. - kw + */ + temp = HTEscape(curdir, URL_PATH); + LYAddHtmlSep(&temp); + StrAllocCat(temp, old_string); + } else { + temp = HTEscape(temp2, URL_PATH); + if (fragment != NULL) { + restorePoundSelector(fragment); + StrAllocCat(temp, fragment); + } + } + } + FREE(cp); + FREE(cp2); + } + if (is_local == FALSE) { + /* + * It's not an accessible subdirectory or file on the local + * system, so assume it's a URL request and guess the scheme + * with "http://" as the default. + */ + CTRACE((tfp, "Can't stat() or fopen() '%s'\n", + temp2 ? temp2 : temp)); +#ifdef WIN_EX /* 1998/01/13 (Tue) 09:07:37 */ + { + const char *p, *q; + char buff[LY_MAXPATH + 128]; + + p = Home_Dir(); + q = temp2 ? temp2 : temp; + + if (strlen(q) == 3 && LYIsDosDrive(q)) { + sprintf(buff, + "'%s' not exist, Goto LynxHome '%s'.", q, p); + _statusline(buff); + LYSleepAlert(); + FREE(temp); + StrAllocCat(*AllocatedString, p); + goto Retry; + } + } +#endif + if (LYExpandHostForURL(&old_string, + URLDomainPrefixes, + URLDomainSuffixes)) { + LYAddSchemeForURL(&old_string); + StrAllocCopy(*AllocatedString, old_string); + } else if (fixit) { + StrAllocCopy(*AllocatedString, old_string); + } else { + /* Return file URL for the file that does not exist */ + StrAllocCat(*AllocatedString, temp); + } +#ifdef WIN_EX + Retry: +#endif + CTRACE((tfp, "Trying: '%s'\n", *AllocatedString)); + } + FREE(temp); + FREE(temp2); + } +#endif /* VMS */ + } else { + /* + * Path begins with a slash. Simplify and use it. + */ + if (old_string[1] == '\0') { + /* + * Request for root. Respect it on Unix, but on VMS we treat that + * as a listing of the login directory. - FM + */ +#ifdef VMS + StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir())); +#else + StrAllocCat(*AllocatedString, "/"); + } else if ((stat(old_string, &st) > -1) || + LYCanReadFile(old_string)) { + /* + * It is an absolute directory or file on the local system. - KW + */ + StrAllocCopy(temp, old_string); + LYTrimRelFromAbsPath(temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp)); + cp = HTEscape(temp, URL_PATH); + StrAllocCat(*AllocatedString, cp); + FREE(cp); + FREE(temp); + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); +#endif /* VMS */ + } else if (LYIsTilde(old_string[1])) { + /* + * Has a Home_Dir() reference. Handle it as if there weren't a + * lead slash. - FM + */ + StrAllocCat(*AllocatedString, wwwName(Home_Dir())); + if ((cp = StrChr((old_string + 1), '/')) != NULL) { + /* + * Append rest of path, if present, skipping "user" if "~user" + * was entered, simplifying, and eliminating any residual + * relative elements. - FM + */ + StrAllocCopy(temp, cp); + LYTrimRelFromAbsPath(temp); + StrAllocCat(*AllocatedString, temp); + FREE(temp); + } + } else { + /* + * Normal absolute path. Simplify, trim any residual relative + * elements, and append it. - FM + */ + StrAllocCopy(temp, old_string); + LYTrimRelFromAbsPath(temp); + StrAllocCat(*AllocatedString, temp); + FREE(temp); + } + CTRACE((tfp, "Converted '%s' to '%s'\n", + old_string, *AllocatedString)); + } + FREE(old_string); + /* Pause so we can read the messages before invoking curses */ + CTRACE_SLEEP(AlertSecs); +} + +#if defined(_WINDOWS) /* 1998/06/23 (Tue) 16:45:20 */ + +int win32_check_interrupt(void) +{ + int c; + + if (kbhit()) { + c = LYgetch(); + /** Keyboard 'Z' or 'z', or Control-G or Control-C **/ + if (LYCharIsINTERRUPT(c) || c == 0x1b) { + return TRUE; + } + } + return FALSE; +} + +#if (!defined(__MINGW32__) && !defined(sleep)) || (defined(__MINGW32__) && !defined(HAVE_SLEEP)) +void sleep(unsigned sec) +{ + unsigned int i, j; + + for (j = 0; j < sec; j++) { + for (i = 0; i < 10; i++) { + Sleep(100); + if (kbhit()) { + (void) LYgetch(); + return; + } + } + } +} +#endif /* !__MINGW32__ */ +#endif /* _WINDOWS */ +/* + * This function expects an absolute Unix or VMS SHELL path spec as an + * allocated string, simplifies it, and trims out any residual relative + * elements. It also checks whether the path had a terminal slash, and if it + * didn't, makes sure that the simplified path doesn't either. If it's a + * directory, our convention is to exclude "Up to parent" links when a terminal + * slash is present. - FM + */ +void LYTrimRelFromAbsPath(char *path) +{ + char *cp; + int i; + BOOL TerminalSlash; + + /* + * Make sure we have a pointer to an absolute path. - FM + */ + if (path == NULL || !LYIsPathSep(*path)) + return; + + /* + * Check whether the path has a terminal slash. - FM + */ + TerminalSlash = (BOOL) (LYIsPathSep(path[(strlen(path) - 1)])); + + /* + * Simplify the path and then do any necessary trimming. - FM + */ + HTSimplify(path, TRUE); + cp = path; + while (cp[1] == '.') { + if (cp[2] == '\0') { + /* + * Eliminate trailing dot. - FM + */ + cp[1] = '\0'; + } else if (LYIsPathSep(cp[2])) { + /* + * Skip over the "/." of a "/./". - FM + */ + cp += 2; + } else if (cp[2] == '.' && cp[3] == '\0') { + /* + * Eliminate trailing dotdot. - FM + */ + cp[1] = '\0'; + } else if (cp[2] == '.' && cp[3] == '/') { + /* + * Skip over the "/.." of a "/../". - FM + */ + cp += 3; + } else { + /* + * Done trimming. - FM + */ + break; + } + } + + /* + * Load any shifts into path, and eliminate any terminal slash created by + * HTSimplify() or our walk, but not present originally. - FM + */ + if (cp > path) { + for (i = 0; cp[i] != '\0'; i++) + path[i] = cp[i]; + path[i] = '\0'; + } + if (TerminalSlash == FALSE) { + LYTrimPathSep(path); + } +} + +/* + * Example Client-Side Include interface. + * + * This is called from SGML.c and simply returns markup for reporting the URL + * of the document being loaded if a comment begins with "<!--#lynxCSI". The + * markup will be included as if it were in the document. Move this function + * to a separate module for doing this kind of thing seriously, someday. - FM + */ +void LYDoCSI(char *url, + const char *comment, + char **csi) +{ + const char *cp = comment; + + if (cp == NULL) + return; + + if (StrNCmp(cp, "!--#", 4)) + return; + + cp += 4; + if (!strncasecomp(cp, "lynxCSI", 7)) { + StrAllocCat(*csi, "\n<p align=\"center\">URL: "); + StrAllocCat(*csi, url); + StrAllocCat(*csi, "</p>\n\n"); + } + + return; +} + +#ifdef VMS +/* + * Define_VMSLogical -- Fote Macrides 04-Apr-1995 + * Define VMS logicals in the process table. + */ +void Define_VMSLogical(char *LogicalName, + char *LogicalValue) +{ + $DESCRIPTOR(lname, ""); + $DESCRIPTOR(lvalue, ""); + $DESCRIPTOR(ltable, "LNM$PROCESS"); + + if (isEmpty(LogicalName)) + return; + + lname.dsc$w_length = strlen(LogicalName); + lname.dsc$a_pointer = LogicalName; + + if (isEmpty(LogicalValue)) { + lib$delete_logical(&lname, <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", NonNull(path), path ? "OK" : "ERR")); + return path; +} + +/* + * Lookup various possibilities for $HOME, and check that the directory exists. + */ +static char *HomeEnv(void) +{ + char *result = CheckDir(LYGetEnv("HOME")); + +#if defined (USE_DOS_DRIVES) && defined(_WIN32) + if (result == 0) { + char *head; + char *leaf; + static char *temp = NULL; + + result = w32_get_shell_folder("Personal"); + if (result == 0) { + /* Windows Vista/7 */ + if ((head = LYGetEnv("USERPROFILE")) != 0) { + HTSprintf0(&temp, "%s%sDocuments", head, FILE_SEPARATOR); + result = CheckDir(temp); + if (result == 0) { + /* Windows 2000 */ + HTSprintf0(&temp, "%s%sMy Documents", head, FILE_SEPARATOR); + result = CheckDir(temp); + } + } + } + /* NT4 */ + if (result == 0) { + if ((head = LYGetEnv("HOMEDRIVE")) != 0) { + if ((leaf = LYGetEnv("HOMEPATH")) != 0) { + HTSprintf0(&temp, "%s%s%s", head, FILE_SEPARATOR, leaf); + result = CheckDir(temp); + } + } + } + /* General M$ */ +#ifdef USE_PROGRAM_DIR + if (result == 0) + result = CheckDir(program_dir); +#endif + if (result == 0) + result = CheckDir(LYGetEnv("TEMP")); + if (result == 0) + result = CheckDir(LYGetEnv("TMP")); + if (result == 0) { + if ((head = LYGetEnv("SystemDrive")) != 0) { + HTSprintf0(&temp, "%s%s", head, FILE_SEPARATOR); + result = CheckDir(temp); + } + } + if (result == 0) + result = CheckDir("C:" FILE_SEPARATOR); + } +#endif + + return result; +} + +const char *Home_Dir(void) +{ + static const char *homedir = NULL; + char *cp = NULL; + + if (homedir == NULL) { + if ((cp = HomeEnv()) == NULL) { +#ifdef VMS + if ((cp = LYGetEnv("SYS$LOGIN")) == NULL + && (cp = LYGetEnv("SYS$SCRATCH")) == NULL) { + cp = "sys$scratch:"; + } + StrAllocCopy(HomeDir, cp); +#else +#ifdef UNIX +#ifdef HAVE_UTMP + /* + * One could use getlogin() and getpwnam() here instead. + */ + struct passwd *pw = getpwuid(geteuid()); + + if (pw && pw->pw_dir) { + StrAllocCopy(HomeDir, pw->pw_dir); + } else +#endif + { + /* + * Use /tmp; it should be writable. + */ + StrAllocCopy(HomeDir, "/tmp"); + } +#endif +#endif /* VMS */ + } else { + StrAllocCopy(HomeDir, cp); + } + homedir = (const char *) HomeDir; +#ifdef LY_FIND_LEAKS + atexit(LYHomeDir_free); +#endif + } + if (homedir == NULL) { + printf("%s\n", gettext("Cannot find HOME directory")); + exit_immediately(EXIT_FAILURE); + } + return homedir; +} + +/* + * Return a pointer to the final leaf of the given pathname, If no pathname + * separators are found, returns the original pathname. The leaf may be + * empty. + */ +char *LYPathLeaf(char *pathname) +{ + char *leaf; + +#ifdef UNIX + if ((leaf = strrchr(pathname, '/')) != 0) { + leaf++; + } +#else +#ifdef VMS + if ((leaf = strrchr(pathname, ']')) == 0) + leaf = strrchr(pathname, ':'); + if (leaf != 0) + leaf++; +#else + int n; + + for (leaf = 0, n = (int) strlen(pathname) - 1; n >= 0; n--) { + if (StrChr("\\/:", pathname[n]) != 0) { + leaf = pathname + n + 1; + break; + } + } +#endif +#endif + return (leaf != 0) ? leaf : pathname; +} + +/* + * This function checks the acceptability of file paths that are intended to be + * off the home directory. The file path should be passed in fbuffer, together + * with the size of the buffer. The function simplifies the file path, and if + * it is acceptable, loads it into fbuffer and returns TRUE. Otherwise, it + * does not modify fbuffer and returns FALSE. If a subdirectory is present and + * the path does not begin with "./", that is prefixed to make the situation + * clear. - FM + */ +BOOLEAN LYPathOffHomeOK(char *fbuffer, + size_t fbuffer_size) +{ + char *file = NULL; + char *cp, *cp1; + + /* + * Make sure we have an fbuffer and a string in it. - FM + */ + if (fbuffer_size < 2 || isEmpty(fbuffer)) { + return (FALSE); + } + StrAllocCopy(file, fbuffer); + cp = file; + + /* + * Check for an inappropriate reference to the home directory, and correct + * it if we can. - FM + */ +#ifdef VMS + if (!strncasecomp(cp, "sys$login", 9)) { + if (*(cp + 9) == '\0') { + /* + * Reject "sys$login". - FM + */ + FREE(file); + return (FALSE); + } + if (*(cp + 9) == ':') { + cp += 10; + if (*cp == '\0') { + /* + * Reject "sys$login:". Otherwise, we have converted + * "sys$login:file" to "file", or have left a strange path for + * VMS as it was originally. - FM + */ + FREE(file); + return (FALSE); + } + } + } +#endif /* VMS */ + if (LYIsTilde(cp[0])) { + if (LYIsPathSep(cp[1])) { + if (cp[2] != '\0') { + if (StrChr((cp + 2), '/') != NULL) { + /* + * Convert "~/subdir(s)/file" to "./subdir(s)/file". - FM + */ + *cp = '.'; + } else { + /* + * Convert "~/file" to "file". - FM + */ + cp += 2; + } + } else { + /* + * Reject "~/". - FM + */ + FREE(file); + return (FALSE); + } + } else if ((*(cp + 1) != '\0') && + (cp1 = StrChr((cp + 1), '/')) != NULL) { + cp = (cp1 - 1); + if (*(cp + 2) != '\0') { + if (StrChr((cp + 2), '/') != NULL) { + /* + * Convert "~user/subdir(s)/file" to "./subdir(s)/file". + * If user is someone else, we covered a spoof. Otherwise, + * we simplified. - FM + */ + *cp = '.'; + } else { + /* + * Convert "~user/file" to "file". - FM + */ + cp += 2; + } + } else { + /* + * Reject "~user/". - FM + */ + FREE(file); + return (FALSE); + } + } else { + /* + * Reject "~user". - FM + */ + FREE(file); + return (FALSE); + } + } +#ifdef VMS + /* + * Check for VMS path specs, and reject if still present. - FM + */ + if (StrChr(cp, ':') != NULL || StrChr(cp, ']') != NULL) { + FREE(file); + return (FALSE); + } +#endif /* VMS */ + + /* + * Check for a URL or absolute path, and reject if present. - FM + */ + if (is_url(cp) || LYIsPathSep(*cp)) { + FREE(file); + return (FALSE); + } + + /* + * Simplify it. - FM + */ + HTSimplify(cp, FALSE); + + /* + * Check if it has a pointless "./". - FM + */ + if (!StrNCmp(cp, "./", 2)) { + if (StrChr((cp + 2), '/') == NULL) { + cp += 2; + } + } + + /* + * Check for spoofing. - FM + */ + if (*cp == '\0' + || LYIsPathSep(*cp) + || LYIsPathSep(cp[(strlen(cp) - 1)]) + || strstr(cp, "..") != NULL + || !strcmp(cp, ".")) { + FREE(file); + return (FALSE); + } + + /* + * Load what we have at this point into fbuffer, trimming if too long, and + * claim it's OK. - FM + */ + if (fbuffer_size > 3 && StrNCmp(cp, "./", 2) && StrChr(cp, '/')) { + /* + * We have a subdirectory and no lead "./", so prefix it to make the + * situation clear. - FM + */ + strcpy(fbuffer, "./"); + if (strlen(cp) > (fbuffer_size - 3)) + cp[(fbuffer_size - 3)] = '\0'; + strcat(fbuffer, cp); + } else { + if (strlen(cp) > (fbuffer_size - 1)) + cp[(fbuffer_size - 1)] = '\0'; + strcpy(fbuffer, cp); + } + FREE(file); + return (TRUE); +} + +/* + * Search for a leading tilde, optionally embedded. If found, return a pointer + * to the tilde. If not found, return the original parameter. + */ +static char *FindLeadingTilde(char *pathname, int embedded) +{ + char *result = pathname; + + if (pathname != NULL) { + if (embedded) { + while (pathname[0] != '\0') { + if (LYIsPathSep(pathname[0])) { + if (LYIsTilde(pathname[1])) { + ++pathname; + break; + } + } + ++pathname; + } + } + if (LYIsTilde(*pathname)) + result = pathname; + } + return result; +} + +/* + * Convert a non-absolute path to one which is off the home directory. Expand + * tildes as a side-effect. Return a pointer to the converted result. + */ +char *LYAbsOrHomePath(char **fname) +{ + if (*fname && !LYisAbsPath(*fname)) { + if (LYIsTilde((*fname)[0])) { + LYTildeExpand(fname, FALSE); + } else { + char temp[LY_MAXPATH]; + + LYAddPathToHome(temp, sizeof(temp), *fname); + StrAllocCopy(*fname, temp); + } + } + return *fname; +} + +/* + * Expand a "leading" tilde into the user's home directory in WWW format. If + * "embedded" is true, allow that "leading" tilde to follow a path separator. + */ +char *LYTildeExpand(char **pathname, + int embedded) +{ + char *temp = FindLeadingTilde(*pathname, embedded); + + if (LYIsTilde(temp[0])) { + + CTRACE((tfp, "LYTildeExpand %s\n", *pathname)); + if (LYIsPathSep(temp[1])) { + char *first = NULL; + char *second = NULL; + + StrAllocCopy(first, *pathname); + first[temp - *pathname] = '\0'; + + StrAllocCopy(second, temp + 2); + + StrAllocCopy(*pathname, first); + StrAllocCat(*pathname, wwwName(Home_Dir())); + LYAddPathSep(pathname); + StrAllocCat(*pathname, second); + + FREE(first); + FREE(second); + } else if (temp[1] == '\0') { + StrAllocCopy(*pathname, wwwName(Home_Dir())); +#ifndef NOUSERS + } else { + char *save; + char saved = '\0'; + struct passwd *pw; + + for (save = temp; *save != '\0'; ++save) { + if (LYIsPathSep(*save)) { + saved = *save; + *save = '\0'; + break; + } + } + pw = getpwnam(temp + 1); + *save = saved; + if (pw != 0 && non_empty(pw->pw_dir)) { + temp = NULL; + StrAllocCopy(temp, save); + StrAllocCopy(*pathname, pw->pw_dir); + StrAllocCat(*pathname, temp); + FREE(temp); + } +#endif + } + CTRACE((tfp, "expanded path %s\n", *pathname)); + } + return *pathname; +} + +/* + * This function appends fname to the home path and returns the full path and + * filename. The fname string can be just a filename (e.g., + * "lynx_bookmarks.html"), or include a subdirectory off the home directory, in + * which case fname should begin with "./" (e.g., ./BM/lynx_bookmarks.html) Use + * LYPathOffHomeOK() to check and/or fix up fname before calling this function. + * On VMS, the resultant full path and filename are converted to VMS syntax. - + * FM + */ +void LYAddPathToHome(char *fbuffer, + size_t fbuffer_size, + const char *fname) +{ + char *home = NULL; + const char *file = fname; + int len; + + /* + * Make sure we have a buffer. - FM + */ + if (!fbuffer) + return; + if (fbuffer_size < 2) { + fbuffer[0] = '\0'; + return; + } + fbuffer[(fbuffer_size - 1)] = '\0'; + + /* + * Make sure we have a file name. - FM + */ + if (!file) + file = ""; + + /* + * Set up home string and length. - FM + */ + StrAllocCopy(home, Home_Dir()); + +#ifdef VMS +#define NO_HOMEPATH "Error:" +#else +#define NO_HOMEPATH "/error" +#endif /* VMS */ + if (!non_empty(home)) + /* + * Home_Dir() has a bug if this ever happens. - FM + */ + StrAllocCopy(home, NO_HOMEPATH); + + len = (int) fbuffer_size - ((int) strlen(home) + 1); + if (len <= 0) { + /* + * Buffer is smaller than or only big enough for the home path. Load + * what fits of the home path and return. This will fail, but we need + * something in the buffer. - FM + */ + LYStrNCpy(fbuffer, home, (fbuffer_size - 1)); + FREE(home); + return; + } +#ifdef VMS + /* + * Check whether we have a subdirectory path or just a filename. - FM + */ + if (!StrNCmp(file, "./", 2)) { + /* + * We have a subdirectory path. - FM + */ + if (home[strlen(home) - 1] == ']') { + /* + * We got the home directory, so convert it to SHELL syntax and + * append subdirectory path, then convert that to VMS syntax. - FM + */ + char *temp = NULL; + + HTSprintf0(&temp, "%s%s", HTVMS_wwwName(home), (file + 1)); + sprintf(fbuffer, "%.*s", + (fbuffer_size - 1), HTVMS_name("", temp)); + FREE(temp); + } else { + /* + * This will fail, but we need something in the buffer. - FM + */ + sprintf(fbuffer, "%s%.*s", home, len, file); + } + } else { + /* + * We have a file in the home directory. - FM + */ + sprintf(fbuffer, "%s%.*s", home, len, file); + } +#else + /* + * Check whether we have a subdirectory path or just a filename. - FM + */ + sprintf(fbuffer, "%s/%.*s", home, len, + (StrNCmp(file, "./", 2) ? file : (file + 2))); +#endif /* VMS */ + FREE(home); +} + +/* + * Given a filename, concatenate it to the save-space pathname, unless it is + * an absolute pathname. If there is no save-space defined, use the home + * directory. Return a new string with the result. + */ +char *LYAddPathToSave(char *fname) +{ + char *result = NULL; + + if (LYisAbsPath(fname)) { + StrAllocCopy(result, fname); + } else { + if (non_empty(lynx_save_space)) { + StrAllocCopy(result, lynx_save_space); + } else { + char temp[LY_MAXPATH]; + + LYAddPathToHome(temp, sizeof(temp), fname); + StrAllocCopy(result, temp); + } + } + return result; +} + +#if !defined(HAVE_PUTENV) && !defined(_WINDOWS) +/* + * No putenv on the NeXT so we use this code instead! + */ + +/* Copyright (C) 1991 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#if defined(STDC_HEADERS) || defined(USG) +#include <string.h> +#else /* Not (STDC_HEADERS or USG): */ +#include <strings.h> +#endif /* STDC_HEADERS or USG */ + +#ifndef NULL +#define NULL 0 +#endif /* !NULL */ + +extern char **environ; + +/* + * Put STRING, which is of the form "NAME=VALUE", in the environment. + */ +int putenv(const char *string) +{ + char *name_end = StrChr(string, '='); + register size_t size; + register char **ep; + + if (name_end == NULL) { + /* Remove the variable from the environment. */ + size = strlen(string); + for (ep = environ; *ep != NULL; ++ep) + if (!StrNCmp(*ep, string, size) && (*ep)[size] == '=') { + while (ep[1] != NULL) { + ep[0] = ep[1]; + ++ep; + } + *ep = NULL; + return 0; + } + } + + size = 0; + for (ep = environ; *ep != NULL; ++ep) + if (!StrNCmp(*ep, string, name_end - string) && + (*ep)[name_end - string] == '=') + break; + else + ++size; + + if (*ep == NULL) { + static char **last_environ = NULL; + char **new_environ = (char **) malloc((size + 2) * sizeof(char *)); + + if (new_environ == NULL) + return -1; + (void) memcpy((char *) new_environ, (char *) environ, size * sizeof(char *)); + + new_environ[size] = (char *) string; + new_environ[size + 1] = NULL; + if (last_environ != NULL) + FREE(last_environ); + last_environ = new_environ; + environ = new_environ; + } else + *ep = (char *) string; + + return 0; +} +#endif /* !HAVE_PUTENV */ + +#ifdef NEED_REMOVE +int remove(char *name) +{ + return unlink(name); +} +#endif + +#if defined(MULTI_USER_UNIX) + +#if defined(HAVE_LSTAT) && defined(S_IFLNK) +/* + * If IsOurFile() is checking a symbolic link, ensure that the target + * points to the user's file as well. + */ +static BOOL IsOurSymlink(const char *name) +{ + BOOL result = FALSE; + size_t size = LY_MAXPATH; + size_t used; + char *buffer = typeMallocn(char, (unsigned) size); + char *check; + + if (buffer != 0) { + while ((used = (size_t) readlink(name, buffer, (size - 1))) == size - 1) { + check = typeRealloc(char, buffer, (unsigned) (size *= 2)); + + if (check == 0) + break; + buffer = check; + } + if (buffer != 0) { + if ((int) used > 0) { + buffer[used] = '\0'; + } else { + FREE(buffer); + } + } + } + if (buffer != 0) { + if (!LYisAbsPath(buffer)) { + char *cutoff = LYLastPathSep(name); + char *clone = NULL; + + if (cutoff != 0) { + HTSprintf0(&clone, "%.*s%s%s", + (int) (cutoff - name), + name, FILE_SEPARATOR, buffer); + FREE(buffer); + buffer = clone; + } + } + CTRACE2(TRACE_CFG, (tfp, "IsOurSymlink(%s -> %s)\n", name, buffer)); + result = IsOurFile(buffer); + FREE(buffer); + } + return result; +} +#endif + +/* + * Verify if this is really a file which is not accessed by a symbolic link, + * except for the special case of its directory being pointed to by a link from + * a directory owned by root and not writable by other users. + */ +BOOL IsOurFile(const char *name) +{ + BOOL result = FALSE; + struct stat data; + + if (!LYIsTilde(name[0]) + && lstat(name, &data) == 0 + && ((S_ISREG(data.st_mode) + && (data.st_mode & (S_IWOTH | S_IWGRP)) == 0 + && data.st_uid == getuid()) +#if defined(HAVE_LSTAT) && defined(S_IFLNK) + || (S_ISLNK(data.st_mode) && IsOurSymlink(name)) +#endif + )) { + int linked = FALSE; + + /* + * ( If this is not a single-user system, the other user is presumed by + * some people busy trying to use a symlink attack on our files ;-) + */ +#if defined(HAVE_LSTAT) + char *path = 0; + char *leaf; + + StrAllocCopy(path, name); + do { + if ((leaf = LYPathLeaf(path)) != path) + *--leaf = '\0'; /* write a null on the '/' */ + if (lstat(*path ? path : "/", &data) != 0) { + break; + } + /* + * If we find a symbolic link, it has to be in a directory that's + * protected. Otherwise someone could have switched it to point + * to one of the real user's files. + */ + if (S_ISLNK(data.st_mode)) { + linked = TRUE; /* could be link-to-link; doesn't matter */ + } else if (S_ISDIR(data.st_mode)) { + if (linked) { + linked = FALSE; + /* + * We assume that a properly-configured system has the + * unwritable directories owned by root. This is not + * necessarily so (bin, news, etc., may), but the only + * uid we can count on is 0. It would be nice to add a + * check for the gid also, but that wouldn't be + * portable. + * + * Likewise, the t-bit would be nice to rely upon. However + * it is marked as an extension in POSIX, rather than + * required. + */ + if (data.st_uid != 0 + || (data.st_mode & S_IWOTH) != 0) { + linked = TRUE; /* force an error-return */ + break; + } + } + } else if (linked) { + break; + } + } while (leaf != path); + FREE(path); +#endif + result = (BOOLEAN) !linked; + } + CTRACE2(TRACE_CFG, (tfp, "IsOurFile(%s) %d\n", name, result)); + return result; +} + +/* + * Open a file that we don't want other users to see. + */ +static FILE *OpenHiddenFile(const char *name, const char *mode) +{ + FILE *fp = 0; + struct stat data; + BOOLEAN binary = (BOOLEAN) (StrChr(mode, 'b') != 0); + +#if defined(O_CREAT) && defined(O_EXCL) /* we have fcntl.h or kindred? */ + /* + * This is the preferred method for creating new files, since it ensures + * that no one has an existing file or link that they happen to own. + */ + if (*mode == 'w') { + int fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD); + + if (fd < 0 + && errno == EEXIST + && IsOurFile(name)) { + if (remove(name) == 0) { + /* FIXME: there's a race at this point if directory is open */ + fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD); + } + } + if (fd >= 0) { +#if defined(O_BINARY) && defined(__CYGWIN__) + if (binary) + setmode(fd, O_BINARY); +#endif + fp = fdopen(fd, mode); + } + } else +#endif + if (*mode == 'a') { + if (IsOurFile(name) + && chmod(name, HIDE_CHMOD) == 0) + fp = fopen(name, mode); + else if (lstat(name, &data) != 0) + fp = OpenHiddenFile(name, binary ? BIN_W : TXT_W); + /* + * This is less stringent, but reasonably portable. For new files, the + * umask will suffice; however if the file already exists we'll change + * permissions first, before opening it. If the chmod fails because of + * some reason other than a non-existent file, there's no point in trying + * to open it. + * + * This won't work properly if the user is root, since the chmod succeeds. + */ + } else if (*mode != 'a') { + mode_t save = umask(HIDE_UMASK); + + if (chmod(name, HIDE_CHMOD) == 0 || errno == ENOENT) + fp = fopen(name, mode); + (void) umask(save); + } + return fp; +} +#else +#define OpenHiddenFile(name, mode) fopen(name, mode) +#endif /* MULTI_USER_UNIX */ + +FILE *LYNewBinFile(const char *name) +{ +#ifdef VMS + FILE *fp = fopen(name, BIN_W, "mbc=32"); + + (void) chmod(name, HIDE_CHMOD); +#else + FILE *fp = OpenHiddenFile(name, BIN_W); +#endif + return fp; +} + +FILE *LYNewTxtFile(const char *name) +{ + FILE *fp; + +#ifdef VMS + fp = fopen(name, TXT_W, "shr=get"); + (void) chmod(name, HIDE_CHMOD); +#else + SetDefaultMode(O_TEXT); + + fp = OpenHiddenFile(name, TXT_W); + + SetDefaultMode(O_BINARY); +#endif + + return fp; +} + +FILE *LYAppendToTxtFile(const char *name) +{ + FILE *fp; + +#ifdef VMS + fp = fopen(name, TXT_A, "shr=get"); + (void) chmod(name, HIDE_CHMOD); +#else + SetDefaultMode(O_TEXT); + + fp = OpenHiddenFile(name, TXT_A); + + SetDefaultMode(O_BINARY); +#endif + return fp; +} + +#if defined(MULTI_USER_UNIX) +/* + * Restore normal permissions to a copy of a file that we have created with + * temp file restricted permissions. The normal umask should apply for user + * files. - kw + */ +void LYRelaxFilePermissions(const char *name) +{ + mode_t mode; + struct stat stat_buf; + + if (stat(name, &stat_buf) == 0 && + S_ISREG(stat_buf.st_mode) && + (mode = (stat_buf.st_mode & 0777)) == HIDE_CHMOD) { + /* + * It looks plausible that this is a file we created with temp file + * paranoid permissions (and the umask wasn't even more restrictive + * when it was copied). - kw + */ + mode_t save = umask(HIDE_UMASK); + + mode = (mode_t) (((mode & 0700) | 0066) & ~save); + (void) umask(save); + (void) chmod(name, mode); + } +} +#endif + +/* + * Check if the given anchor has an associated file-cache. + */ +BOOLEAN LYCachedTemp(char *target, + char **cached) +{ + BOOLEAN result = FALSE; + + if (*cached) { + LYStrNCpy(target, *cached, LY_MAXPATH); + FREE(*cached); + if (LYCanReadFile(target)) { + if (remove(target) != 0) { + CTRACE((tfp, "cannot remove %s\n", target)); + } + } + result = TRUE; + } + return result; +} + +#ifndef HAVE_MKDTEMP +#define mkdtemp(path) ((mktemp(path) != 0) && (mkdir(path, 0700) == 0)) +#endif + +/* + * Open a temp-file, ensuring that it is unique, and not readable by other + * users. + * + * The mode can be one of: "w", "a", "wb". + */ +FILE *LYOpenTemp(char *result, + const char *suffix, + const char *mode) +{ + FILE *fp = 0; + BOOL txt = TRUE; + char wrt = 'r'; + LY_TEMP *p; + + CTRACE((tfp, "LYOpenTemp(,%s,%s)\n", suffix, mode)); + if (result == 0) + return 0; + + while (*mode != '\0') { + switch (*mode++) { + case 'w': + wrt = 'w'; + break; + case 'a': + wrt = 'a'; + break; + case 'b': + txt = FALSE; + break; + default: + CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__)); + return 0; + } + } + + /* + * Verify if the given space looks secure enough. Otherwise, make a + * secure subdirectory of that. + */ +#if defined(MULTI_USER_UNIX) && (defined(HAVE_MKTEMP) || defined(HAVE_MKDTEMP)) + if (lynx_temp_subspace == 0) { + BOOL make_it = FALSE; + struct stat sb; + + if (lstat(lynx_temp_space, &sb) == 0 + && S_ISDIR(sb.st_mode)) { + if (sb.st_uid != getuid() + || (sb.st_mode & (S_IWOTH | S_IWGRP)) != 0) { + make_it = TRUE; + CTRACE((tfp, + "lynx_temp_space is not our directory %s owner %d mode %03o\n", + lynx_temp_space, + (int) sb.st_uid, + (unsigned) (sb.st_mode & 0777))); + } + } else { + make_it = TRUE; + CTRACE((tfp, "lynx_temp_space is not a directory %s\n", lynx_temp_space)); + } + if (make_it) { + mode_t old_mask = umask(HIDE_UMASK); + + StrAllocCat(lynx_temp_space, "lynxXXXXXXXXXX"); + if (mkdtemp(lynx_temp_space) == 0) { + printf("%s: %s\n", lynx_temp_space, LYStrerror(errno)); + exit_immediately(EXIT_FAILURE); + } + (void) umask(old_mask); + lynx_temp_subspace = 1; + StrAllocCat(lynx_temp_space, "/"); + CTRACE((tfp, "made subdirectory %s\n", lynx_temp_space)); + } else { + lynx_temp_subspace = -1; + } + } +#endif + + do { + if (!fmt_tempname(result, lynx_temp_space, suffix)) + return 0; + if (txt) { + switch (wrt) { + case 'w': + fp = LYNewTxtFile(result); + break; + case 'a': + fp = LYAppendToTxtFile(result); + break; + } + } else { + fp = LYNewBinFile(result); + } + /* + * If we get a failure to make a temporary file, don't bother to try a + * different name unless the failure was because the file already + * exists. + */ +#ifdef EEXIST /* FIXME (need a better test) in fcntl.h or unistd.h */ + if ((fp == 0) && (errno != EEXIST)) { + CTRACE((tfp, "... LYOpenTemp(%s) failed: %s\n", + result, LYStrerror(errno))); + return 0; + } +#endif + } while (fp == 0); + + if ((p = typecalloc(LY_TEMP)) != 0) { + p->next = ly_temp; + StrAllocCopy((p->name), result); + p->file = fp; + p->outs = (BOOLEAN) (wrt != 'r'); + ly_temp = p; + } else { + outofmem(__FILE__, "LYOpenTemp"); + } + + CTRACE((tfp, "... LYOpenTemp(%s)\n", result)); + return fp; +} + +/* + * Reopen a temporary file + */ +FILE *LYReopenTemp(char *name) +{ + LY_TEMP *p; + FILE *fp = 0; + + LYCloseTemp(name); + if ((p = FindTempfileByName(name)) != 0) { + fp = p->file = LYAppendToTxtFile(name); + } + return fp; +} + +/* + * Open a temp-file for writing, possibly re-using a previously used + * name and file. + * If a non-empty fname is given, it is reused if it indicates a file + * previously registered as a temp file and, in case the file still + * exists, if it looks like we can write to it safely. Otherwise a + * new temp file (with new name) will be generated and returned in fname. + * + * File permissions are set so that the file is not readable by unprivileged + * other users. + * + * Suffix is only used if fname is not being reused. + * The mode should be "w", others are possible (they may be passed on) + * but probably don't make sense. - kw + */ +FILE *LYOpenTempRewrite(char *fname, + const char *suffix, + const char *mode) +{ + FILE *fp = 0; + BOOL txt = TRUE; + char wrt = 'r'; + BOOL registered = NO; + BOOL writable_exists = NO; + BOOL is_ours = NO; + BOOL still_open = NO; + LY_TEMP *p; + struct stat stat_buf; + + CTRACE((tfp, "LYOpenTempRewrite(%s,%s,%s)\n", fname, suffix, mode)); + if (*fname == '\0') /* first time, no filename yet */ + return (LYOpenTemp(fname, suffix, mode)); + + if ((p = FindTempfileByName(fname)) != 0) { + registered = YES; + if (p->file != 0) + still_open = YES; + CTRACE((tfp, "...used before%s\n", still_open ? ", still open!" : ".")); + } + + if (registered) { +#ifndef NO_GROUPS + writable_exists = HTEditable(fname); /* existing, can write */ +#define CTRACE_EXISTS "exists and is writable, " +#else + writable_exists = (BOOL) (stat(fname, &stat_buf) == 0); /* existing, assume can write */ +#define CTRACE_EXISTS "exists, " +#endif + + if (writable_exists) { + is_ours = IsOurFile(fname); + } + CTRACE((tfp, "...%s%s\n", + writable_exists ? CTRACE_EXISTS : "", + is_ours ? "is our file." : "is NOT our file.")); + } + + /* + * Note that in cases where LYOpenTemp is called as fallback below, we + * don't call LYRemoveTemp first. That may be appropriate in some cases, + * but not trying to remove a weird existing file seems safer and could + * help diagnose an unusual situation. (They may be removed anyway later.) + */ + if (still_open) { + /* + * This should probably not happen. Make a new one. + */ + return (LYOpenTemp(fname, suffix, mode)); + } else if (!registered) { + /* + * Not registered. It should have been registered at one point though, + * otherwise we wouldn't be called like this. + */ + return (LYOpenTemp(fname, suffix, mode)); + } else if (writable_exists && !is_ours) { + /* + * File exists, writable if we checked, but something is wrong with it. + */ + return (LYOpenTemp(fname, suffix, mode)); +#ifndef NO_GROUPS + } else if (!is_ours && (lstat(fname, &stat_buf) == 0)) { + /* + * Exists but not writable, and something is wrong with it. + */ + return (LYOpenTemp(fname, suffix, mode)); +#endif + } + + while (*mode != '\0') { + switch (*mode++) { + case 'w': + wrt = 'w'; + break; + case 'a': + wrt = 'a'; + break; + case 'b': + txt = FALSE; + break; + default: + CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__)); + return fp; + } + } + + if (is_ours) { + /* + * Yes, it exists, is writable if we checked, and everything looks ok + * so far. This should be the most regular case. - kw + */ +#ifdef HAVE_TRUNCATE + if (txt == TRUE) { /* limitation of LYReopenTemp. shrug */ + /* + * We truncate and then append, this avoids having a small window + * in which the file doesn't exist. - kw + */ + if (truncate(fname, (off_t) 0) != 0) { + CTRACE((tfp, "... truncate(%s,0) failed: %s\n", + fname, LYStrerror(errno))); + return (LYOpenTemp(fname, suffix, mode)); + } else { + return (LYReopenTemp(fname)); + } + } +#endif + remove(fname); + + } + + /* We come here in two cases: either the file existed and was ours and we + * just got rid of it. Or the file did and does not exist, but is + * registered as a temp file. It must have been removed by some means + * other than LYRemoveTemp. In both cases, reuse the name! - kw + */ + + if (txt) { + switch (wrt) { + case 'w': + fp = LYNewTxtFile(fname); + break; + case 'a': + fp = LYAppendToTxtFile(fname); + break; + } + } else { + fp = LYNewBinFile(fname); + } + p->file = fp; + + CTRACE((tfp, "... LYOpenTempRewrite(%s), %s\n", fname, + (fp) ? "ok" : "failed")); + /* + * We could fall back to trying LYOpenTemp() here in case of failure. + * After all the checks already done above a filure here should be pretty + * unusual though, so maybe it's better to let the user notice that + * something went wrong, and not try to fix it up. - kw + */ + return fp; +} + +/* + * Special case of LYOpenTemp, used for manipulating bookmark file, i.e., with + * renaming. + */ +FILE *LYOpenScratch(char *result, + const char *prefix) +{ + FILE *fp; + LY_TEMP *p; + + if (!fmt_tempname(result, prefix, HTML_SUFFIX)) + return 0; + + if ((fp = LYNewTxtFile(result)) != 0) { + if ((p = typecalloc(LY_TEMP)) != 0) { + p->next = ly_temp; + StrAllocCopy((p->name), result); + p->file = fp; + ly_temp = p; + } else { + outofmem(__FILE__, "LYOpenScratch"); + } + } + CTRACE((tfp, "LYOpenScratch(%s)\n", result)); + return fp; +} + +static void LY_close_temp(LY_TEMP * p) +{ + if (p->file != 0) { + if (p->outs) { + LYCloseOutput(p->file); + } else { + LYCloseInput(p->file); + } + p->file = 0; + } +} + +/* + * Close a temp-file, given its name + */ +void LYCloseTemp(char *name) +{ + LY_TEMP *p; + + CTRACE((tfp, "LYCloseTemp(%s)\n", name)); + if ((p = FindTempfileByName(name)) != 0) { + CTRACE((tfp, "...LYCloseTemp(%s)%s\n", name, + (p->file != 0) ? ", closed" : "")); + LY_close_temp(p); + } +} + +/* + * Close a temp-file, given its file-pointer + */ +void LYCloseTempFP(FILE *fp) +{ + LY_TEMP *p; + + CTRACE((tfp, "LYCloseTempFP\n")); + if ((p = FindTempfileByFP(fp)) != 0) { + LY_close_temp(p); + CTRACE((tfp, "...LYCloseTempFP(%s)\n", p->name)); + } +} + +/* + * Close a temp-file, removing it. + */ +int LYRemoveTemp(char *name) +{ + LY_TEMP *p, *q; + int code = -1; + + if (non_empty(name)) { + CTRACE((tfp, "LYRemoveTemp(%s)\n", name)); + for (p = ly_temp, q = 0; p != 0; q = p, p = p->next) { + if (!strcmp(name, p->name)) { + if (q != 0) { + q->next = p->next; + } else { + ly_temp = p->next; + } + LY_close_temp(p); + code = HTSYS_remove(name); + CTRACE((tfp, "...LYRemoveTemp done(%d)%s\n", code, + (p->file != 0) ? ", closed" : "")); + CTRACE_FLUSH(tfp); + FREE(p->name); + FREE(p); + break; + } + } + } + return code; +} + +/* + * Remove all of the temp-files. Note that this assumes that they are closed, + * since some systems will not allow us to remove a file which is open. + */ +void LYCleanupTemp(void) +{ + while (ly_temp != 0) { + (void) LYRemoveTemp(ly_temp->name); + } +#if defined(MULTI_USER_UNIX) + if (lynx_temp_subspace > 0) { + char result[LY_MAXPATH]; + + LYStrNCpy(result, lynx_temp_space, sizeof(result) - 1); + LYTrimPathSep(result); + CTRACE((tfp, "LYCleanupTemp removing %s\n", result)); + rmdir(result); + lynx_temp_subspace = -1; + } +#endif +} + +/* + * We renamed a temporary file. Keep track so we can remove it on exit. + */ +void LYRenamedTemp(char *oldname, + char *newname) +{ + LY_TEMP *p; + + CTRACE((tfp, "LYRenamedTemp(old=%s, new=%s)\n", oldname, newname)); + if ((p = FindTempfileByName(oldname)) != 0) { + StrAllocCopy((p->name), newname); + } +} + +#ifndef DISABLE_BIBP +/* + * Check that bibhost defines the BibP icon. + */ +void LYCheckBibHost(void) +{ + DocAddress bibhostIcon; + BOOLEAN saveFlag; + + bibhostIcon.address = NULL; + StrAllocCopy(bibhostIcon.address, BibP_bibhost); + StrAllocCat(bibhostIcon.address, "bibp1.0/bibpicon.jpg"); + bibhostIcon.post_data = NULL; + bibhostIcon.post_content_type = NULL; + bibhostIcon.bookmark = FALSE; + bibhostIcon.isHEAD = FALSE; + bibhostIcon.safe = FALSE; + saveFlag = traversal; + traversal = TRUE; /* Hack to force error response. */ + BibP_bibhost_available = (BOOLEAN) (HTLoadAbsolute(&bibhostIcon) == YES); + traversal = saveFlag; + BibP_bibhost_checked = TRUE; +} +#endif /* !DISABLE_BIBP */ + +/* + * Management of User Interface Pages. - kw + * + * These are mostly temp files. Pages which can be recognized by their special + * URL (after having been loaded) need not be tracked here. + * + * First some private stuff: + */ +typedef struct uipage_entry { + UIP_t type; + unsigned flags; + char *url; + HTList *alturls; + char *file; +} uip_entry; + +#define UIP_F_MULTI 0x0001 /* flag: track multiple instances */ +#define UIP_F_LIMIT 0x0002 /* flag: limit size of alturls list */ +#define UIP_F_LMULTI (UIP_F_MULTI | UIP_F_LIMIT) +/* *INDENT-OFF* */ +static uip_entry ly_uip[] = +{ + { UIP_HISTORY , UIP_F_LMULTI, NULL, NULL, NULL } + , { UIP_DOWNLOAD_OPTIONS , 0 , NULL, NULL, NULL } + , { UIP_PRINT_OPTIONS , 0 , NULL, NULL, NULL } + , { UIP_SHOWINFO , UIP_F_LMULTI, NULL, NULL, NULL } + , { UIP_LIST_PAGE , UIP_F_LMULTI, NULL, NULL, NULL } + , { UIP_VLINKS , UIP_F_LMULTI, NULL, NULL, NULL } +#if !defined(NO_OPTION_FORMS) + , { UIP_OPTIONS_MENU , UIP_F_LMULTI, NULL, NULL, NULL } +#endif +#ifdef DIRED_SUPPORT + , { UIP_DIRED_MENU , 0 , NULL, NULL, NULL } + , { UIP_PERMIT_OPTIONS , 0 , NULL, NULL, NULL } + , { UIP_UPLOAD_OPTIONS , UIP_F_LMULTI, NULL, NULL, NULL } +#endif +#ifdef USE_ADDRLIST_PAGE + , { UIP_ADDRLIST_PAGE , UIP_F_LMULTI, NULL, NULL, NULL } +#endif + , { UIP_LYNXCFG , UIP_F_LMULTI, NULL, NULL, NULL } +#if !defined(NO_CONFIG_INFO) + , { UIP_CONFIG_DEF , UIP_F_LMULTI, NULL, NULL, NULL } +#endif +/* The following are not generated tempfiles: */ + , { UIP_TRACELOG , 0 , NULL, NULL, NULL } +#if defined(DIRED_SUPPORT) && defined(OK_INSTALL) + , { UIP_INSTALL , 0 , NULL, NULL, NULL } +#endif + +}; +/* *INDENT-ON* */ + +/* Public entry points for User Interface Page management: */ + +BOOL LYIsUIPage3(const char *url, + UIP_t type, + int flagparam) +{ + unsigned int i; + size_t l; + BOOL result = NO; + + if (url) { + for (i = 0; i < TABLESIZE(ly_uip); i++) { + if (ly_uip[i].type == type) { + if (!ly_uip[i].url) { + break; + } else if ((flagparam & UIP_P_FRAG) ? + (!StrNCmp(ly_uip[i].url, url, (l = strlen(ly_uip[i].url))) + && (url[l] == '\0' || url[l] == '#')) : + !strcmp(ly_uip[i].url, url)) { + result = YES; + } else if (ly_uip[i].flags & UIP_F_MULTI) { + char *p; + HTList *l0 = ly_uip[i].alturls; + + while ((p = (char *) HTList_nextObject(l0)) != NULL) { + if ((flagparam & UIP_P_FRAG) + ? (!StrNCmp(p, url, (l = strlen(p))) && + (url[l] == '\0' || url[l] == '#')) + : !strcmp(p, url)) { + result = YES; + break; + } + } + } + break; + } + } + } + return result; +} + +void LYRegisterUIPage(const char *url, + UIP_t type) +{ + unsigned int i; + + for (i = 0; i < TABLESIZE(ly_uip); i++) { + if (ly_uip[i].type == type) { + if (ly_uip[i].url && url && + !strcmp(ly_uip[i].url, url)) { + + } else if (!ly_uip[i].url || !url || + !(ly_uip[i].flags & UIP_F_MULTI)) { + StrAllocCopy(ly_uip[i].url, url); + + } else { + char *p; + int n = 0; + HTList *l0 = ly_uip[i].alturls; + + while ((p = (char *) HTList_nextObject(l0)) != NULL) { + if (!strcmp(p, url)) + return; + if (!strcmp(p, ly_uip[i].url)) { + StrAllocCopy(ly_uip[i].url, url); + return; + } + n++; + } + if (!ly_uip[i].alturls) + ly_uip[i].alturls = HTList_new(); + + if (n >= HTCacheSize && (ly_uip[i].flags & UIP_F_LIMIT)) + HTList_removeFirstObject(ly_uip[i].alturls); + HTList_addObject(ly_uip[i].alturls, ly_uip[i].url); + ly_uip[i].url = NULL; + StrAllocCopy(ly_uip[i].url, url); + } + + return; + } + } +} + +void LYUIPages_free(void) +{ + unsigned int i; + + for (i = 0; i < TABLESIZE(ly_uip); i++) { + FREE(ly_uip[i].url); + FREE(ly_uip[i].file); + LYFreeStringList(ly_uip[i].alturls); + ly_uip[i].alturls = NULL; + } +} + +/* + * Convert local pathname to www name + * (do not bother about file://localhost prefix at this point). + */ +const char *wwwName(const char *pathname) +{ + const char *cp = NULL; + +#if defined(USE_DOS_DRIVES) + cp = HTDOS_wwwName(pathname); +#else +#ifdef VMS + cp = HTVMS_wwwName(pathname); +#else + cp = pathname; +#endif /* VMS */ +#endif + + return cp; +} + +/* + * Given a user-specified filename, e.g., for download or print, validate and + * expand it. Expand home-directory expressions in the given string. Only + * allow pipes if the user can spawn shell commands. + */ +BOOLEAN LYValidateFilename(bstring **result, + bstring **given) +{ + BOOLEAN code = TRUE; + char *cp = NULL; + + /* + * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path on VMS. + * - FM + */ + if (LYIsNullDevice((*given)->str)) { + /* just ignore it */ + code = FALSE; +#ifdef HAVE_POPEN + } else if (LYIsPipeCommand((*given)->str)) { + if (no_shell) { + HTUserMsg(SPAWNING_DISABLED); + code = FALSE; + } else { + BStrCopy(*result, (*given)); + } +#endif + } else { + if (FindLeadingTilde((*given)->str, TRUE) != 0) { + char *cp1 = NULL; + + StrAllocCopy(cp1, (*given)->str); + LYTildeExpand(&cp1, TRUE); + BStrCopy0(*result, cp1); + BStrCopy0(*given, cp1); + FREE(cp1); + } +#ifdef VMS + if (StrChr((*given)->str, '/') != NULL) { + BStrCopy0(*result, HTVMS_name("", (*given)->str)); + BStrCopy(*given, *result); + } + if ((*given)->str[0] != '/' + && StrChr((*given)->str, ':') == NULL) { + BStrCopy0(*result, "sys$disk:"); + if (StrChr((*given)->str, ']') == NULL) + BStrCat0(*result, "[]"); + BStrCat(*result, (*given)); + } else { + BStrCopy(*result, (*given)); + } +#else + +#ifndef __EMX__ + if (!LYisAbsPath((*given)->str)) { +#if defined(__DJGPP__) || defined(_WINDOWS) + if (StrChr((*result)->str, ':') != NULL) + cp = NULL; + else +#endif /* __DJGPP__ || _WINDOWS */ + { +#ifdef SUPPORT_CHDIR + static char buf[LY_MAXPATH]; + + cp = Current_Dir(buf); +#else + cp = original_dir; +#endif + } + } else +#endif /* __EMX__ */ + cp = NULL; + + if (cp) { + LYTrimPathSep(cp); + BStrCopy0(*result, cp); + BStrCat0(*result, "/"); + } else { + BStrCopy0(*result, ""); + } + if (code) { + cp = HTSYS_name((*given)->str); + BStrCat0(*result, cp); + } +#endif /* VMS */ + } + return code; +} + +/* + * Given a valid filename, check if it exists. If so, we'll have to worry + * about overwriting it. + * + * Returns: + * 'Y' (yes/success) + * 'N' (no/retry) + * 3 (cancel) + */ +int LYValidateOutput(char *filename) +{ + int c; + + CTRACE((tfp, "LYValidateOutput '%s'\n", filename)); + + /* + * Assume we can write to a pipe + */ +#ifdef HAVE_POPEN + if (LYIsPipeCommand(filename)) + return 'Y'; +#endif + + if (no_dotfiles || !show_dotfiles) { + if (*LYPathLeaf(filename) == '.') { + HTAlert(FILENAME_CANNOT_BE_DOT); + return 'N'; + } + } + + /* + * See if it already exists. + */ + if (LYCanReadFile(filename)) { +#ifdef VMS + c = HTConfirm(FILE_EXISTS_HPROMPT); +#else + c = HTConfirm(FILE_EXISTS_OPROMPT); +#endif /* VMS */ + if (HTLastConfirmCancelled()) { + HTInfoMsg(SAVE_REQUEST_CANCELLED); + return 3; + } else if (c == NO) { + return 'N'; + } + } else if (!LYCanWriteFile(filename)) { + return 'N'; + } + return 'Y'; +} + +/* + * Convert a local filename to a URL + */ +void LYLocalFileToURL(char **target, + const char *source) +{ + const char *leaf; + + StrAllocCopy(*target, "file://localhost"); + + leaf = wwwName(source); + + if (!LYisAbsPath(source)) { + char temp[LY_MAXPATH]; + + Current_Dir(temp); + if (!LYIsHtmlSep(*temp)) + LYAddHtmlSep(target); + StrAllocCat(*target, temp); + } + if (leaf && !LYIsHtmlSep(*leaf)) + LYAddHtmlSep(target); + StrAllocCat(*target, leaf); +} + +#define PUT_STRING(buf) (*(target)->isa->put_string)(target, buf) + +/* + * Like WriteInternalTitle, used for writing title on pages constructed via + * streams. + */ +void WriteStreamTitle(HTStream *target, const char *Title) +{ + char *buf = 0; + + PUT_STRING(LYNX_DOCTYPE); + PUT_STRING("<html>\n<head>\n"); + LYAddMETAcharsetToStream(target, -1); + HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n", Title); + PUT_STRING(buf); + FREE(buf); +} + +/* + * Open a temporary file for internal-pages, optionally reusing an existing + * filename. + */ +FILE *InternalPageFP(char *filename, + int reuse_flag) +{ + FILE *fp; + + if (LYReuseTempfiles && reuse_flag) { + fp = LYOpenTempRewrite(filename, HTML_SUFFIX, BIN_W); + } else { + (void) LYRemoveTemp(filename); + fp = LYOpenTemp(filename, HTML_SUFFIX, BIN_W); + } + if (fp == NULL) { + HTAlert(CANNOT_OPEN_TEMP); + } + return fp; +} + +/* + * This part is shared by all internal pages. + */ +void WriteInternalTitle(FILE *fp0, const char *Title) +{ + fprintf(fp0, LYNX_DOCTYPE); + + fprintf(fp0, "<html>\n<head>\n"); + LYAddMETAcharsetToFD(fp0, -1); + if (LYIsListpageTitle(Title)) { + if (StrChr(HTLoadedDocumentURL(), '"') == NULL) { + char *Address = NULL; + + /* + * Insert a BASE tag so there is some way to relate the List Page + * file to its underlying document after we are done. It won't be + * actually used for resolving relative URLs. - kw + */ + StrAllocCopy(Address, HTLoadedDocumentURL()); + LYEntify(&Address, FALSE); + fprintf(fp0, "<base href=\"%s\">\n", Address); + FREE(Address); + } + } + fprintf(fp0, "<title>%s</title>\n</head>\n<body>\n", Title); +} + +/* + * This is used to start most internal pages, except for special cases where + * the embedded HREF's in the title differ. + */ +void BeginInternalPage(FILE *fp0, const char *Title, + const char *HelpURL) +{ + WriteInternalTitle(fp0, Title); + + if ((user_mode == NOVICE_MODE) + && LYwouldPush(Title, NULL) + && (HelpURL != 0)) { + fprintf(fp0, "<h1>%s (%s%s%s), <a href=\"%s%s\">help</a></h1>\n", + Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION, + helpfilepath, HelpURL); + } else { + fprintf(fp0, "<h1>%s (%s%s%s)</h1>\n", + Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION); + } +} + +void EndInternalPage(FILE *fp0) +{ + fprintf(fp0, "</body>\n</html>"); +} + +char *trimPoundSelector(char *address) +{ + char *pound = findPoundSelector(address); + + if (pound != 0) + *pound = '\0'; + return pound; +} + +/* + * Trim a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to local filesystems. + */ +void LYTrimPathSep(char *path) +{ + size_t len; + + if (path != 0 + && (len = strlen(path)) != 0 + && LYIsPathSep(path[len - 1])) + path[len - 1] = 0; +} + +/* + * Add a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to local filesystems. + */ +void LYAddPathSep(char **path) +{ + size_t len; + char *temp; + + if ((path != 0) + && ((temp = *path) != 0) + && (len = strlen(temp)) != 0 + && !LYIsPathSep(temp[len - 1])) { + StrAllocCat(*path, FILE_SEPARATOR); + } +} + +/* + * Add a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to local filesystems. + */ +void LYAddPathSep0(char *path) +{ + size_t len; + + if ((path != 0) + && (len = strlen(path)) != 0 + && (len < LY_MAXPATH - 2) + && !LYIsPathSep(path[len - 1])) { + strcat(path, FILE_SEPARATOR); + } +} + +/* + * Check if a given string contains a path separator + */ +char *LYLastPathSep(const char *path) +{ + char *result; + +#if defined(USE_DOS_DRIVES) + if ((result = strrchr(path, '\\')) == 0) + result = strrchr(path, '/'); +#else + result = strrchr(path, '/'); +#endif + return result; +} + +/* + * Trim a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to HTML paths. + */ +void LYTrimHtmlSep(char *path) +{ + size_t len; + + if (path != 0 + && (len = strlen(path)) != 0 + && LYIsHtmlSep(path[len - 1])) + path[len - 1] = 0; +} + +/* + * Add a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to HTML paths. + */ +void LYAddHtmlSep(char **path) +{ + size_t len; + char *temp; + + if ((path != 0) + && ((temp = *path) != 0) + && (len = strlen(temp)) != 0 + && !LYIsHtmlSep(temp[len - 1])) { + StrAllocCat(*path, "/"); + } +} + +/* + * Add a trailing path-separator to avoid confusing other programs when we concatenate + * to it. This only applies to HTML paths. + */ +void LYAddHtmlSep0(char *path) +{ + size_t len; + + if ((path != 0) + && (len = strlen(path)) != 0 + && (len < LY_MAXPATH - 2) + && !LYIsHtmlSep(path[len - 1])) { + strcat(path, "/"); + } +} + +/* + * Rename a file + */ +int LYRenameFile(char *src, + char *dst) +{ +#ifdef _WINDOWS + /* + * If dest_file exists prior to calling rename(), rename() will fail on Windows platforms. + * https://www.securecoding.cert.org/confluence/display/c/FIO10-C.+Take+care+when+using+the+rename%28%29+function + */ + struct stat st; + + if (stat(dst, &st) == 0) { + unlink(dst); + } +#endif + return rename(src, dst); +} + +/* + * Copy a file + */ +int LYCopyFile(char *src, + char *dst) +{ + int code; + const char *program; + + if ((program = HTGetProgramPath(ppCOPY)) != NULL) { + char *the_command = 0; + + HTAddParam(&the_command, COPY_COMMAND, 1, program); + HTAddParam(&the_command, COPY_COMMAND, 2, src); + HTAddParam(&the_command, COPY_COMMAND, 3, dst); + HTEndParam(&the_command, COPY_COMMAND, 3); + + CTRACE((tfp, "command: %s\n", the_command)); + stop_curses(); + code = LYSystem(the_command); + start_curses(); + + FREE(the_command); + } else { + FILE *fin, *fout; + unsigned char buff[BUFSIZ]; + size_t len; + + code = EOF; + if ((fin = fopen(src, BIN_R)) != 0) { + if ((fout = fopen(dst, BIN_W)) != 0) { + code = 0; + while ((len = fread(buff, (size_t) 1, sizeof(buff), fin)) != 0) { + if (fwrite(buff, (size_t) 1, len, fout) < len + || ferror(fout)) { + code = EOF; + break; + } + } + LYCloseOutput(fout); + } + LYCloseInput(fin); + } + CTRACE((tfp, "builtin copy ->%d\n\tsource=%s\n\ttarget=%s\n", + code, src, dst)); + } + + if (code) { + HTAlert(CANNOT_WRITE_TO_FILE); + } + return code; +} + +#ifdef __DJGPP__ +static char *escape_backslashes(char *source) +{ + char *result = 0; + int count = 0; + int n; + + for (n = 0; source[n] != '\0'; ++n) { + if (source[n] == '\\') + ++count; + } + if (count != 0) { + result = malloc(count + n + 1); + if (result != 0) { + int ch; + char *target = result; + + while ((ch = *source++) != '\0') { + if (ch == '\\') + *target++ = ch; + *target++ = ch; + } + *target = '\0'; + } + } + return result; +} +#endif /* __DJGPP__ */ +/* + * Invoke a shell command, return nonzero on error. + */ +int LYSystem(char *command) +{ + int code; + int do_free = 0; + +#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG) + struct sigaction saved_sigtstp_act; + BOOLEAN sigtstp_saved = FALSE; +#endif + int saved_errno = 0; + +#ifdef __EMX__ + int scrsize[4]; +#endif + + fflush(stdout); + fflush(stderr); + CTRACE((tfp, "LYSystem(%s)\n", command)); + CTRACE_FLUSH(tfp); + +#ifdef __DJGPP__ + __djgpp_set_ctrl_c(0); + _go32_want_ctrl_break(1); +#endif /* __DJGPP__ */ + +#ifdef VMS + code = DCLsystem(command); +#else +# ifdef __EMX__ /* FIXME: Should be LY_CONVERT_SLASH? */ + /* Configure writes commands which contain direct slashes. + Native command-(non)-shell will not tolerate this. */ + { + char *space = command, *slash = command; + + _scrsize(scrsize); + while (*space && *space != ' ' && *space != '\t') + space++; + while (slash < space && *slash != '/') + slash++; + if (slash != space) { + char *old = command; + + command = NULL; + StrAllocCopy(command, old); + do_free = 1; + slash = (slash - old) + command - 1; + space = (space - old) + command; + while (++slash < space) + if (*slash == '/') + *slash = '\\'; + } + } +# endif + + /* + * This chunk of code does not work, for two reasons: + * a) the Cygwin system() function edits out the backslashes + * b) it does not account for more than one parameter, e.g., +number + */ +#if defined(__CYGWIN__) && defined(DOSPATH) /* 1999/02/26 (Fri) */ + { + char cmd[LY_MAXPATH]; + char win32_name[LY_MAXPATH]; + char new_cmd[LY_MAXPATH]; + char new_command[LY_MAXPATH * 2 + 10]; + char *p, *q; + + p = command; + q = cmd; + while (*p) { + if (*p == ' ') + break; + else + *q = *p; + p++; + q++; + } + *q = '\0'; + + if (cmd[0] == '/') + cygwin_conv_to_full_posix_path(cmd, new_cmd); + else + strcpy(new_cmd, cmd); + + while (*p == ' ') + p++; + + if (StrChr(p, '\\') == NULL) { + /* for Windows Application */ + cygwin_conv_to_full_win32_path(p, win32_name); + sprintf(new_command, "%.*s \"%.*s\"", + LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name); + } else { + /* for DOS like editor */ + q = win32_name; + while (*p) { + if (*p == '\\') { + if (*(p + 1) == '\\') + p++; + } + *q = *p; + q++, p++; + } + *q = '\0'; + sprintf(new_command, "%.*s %.*s", LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name); + } + command = new_command; + } +#endif + +#ifdef __DJGPP__ + if (dj_is_bash) { + char *new_command = escape_backslashes(command); + + if (new_command != 0) { + if (do_free) + free(command); + command = new_command; + } + } +#endif /* __DJGPP__ */ + +#ifdef _WIN_CC + code = exec_command(command, TRUE); /* Wait exec */ +#else /* !_WIN_CC */ +#ifdef SIGPIPE + if (restore_sigpipe_for_children) + signal(SIGPIPE, SIG_DFL); /* Some commands expect the default */ +#endif +#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG) + if (!dump_output_immediately && !LYCursesON && !no_suspend) + sigtstp_saved = LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 1); +#endif + code = system(command); + saved_errno = errno; +#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG) + if (sigtstp_saved) + LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 0); +#endif +#ifdef SIGPIPE + if (restore_sigpipe_for_children) + signal(SIGPIPE, SIG_IGN); /* Ignore it again - kw */ +#endif +#endif +#endif + +#ifdef __DJGPP__ + __djgpp_set_ctrl_c(1); + _go32_want_ctrl_break(0); +#endif /* __DJGPP__ */ + + fflush(stdout); + fflush(stderr); + + if (do_free) + FREE(command); +#if !defined(UCX) || !defined(VAXC) /* errno not modifiable ?? */ + set_errno(saved_errno); /* may have been clobbered */ +#endif +#ifdef __EMX__ /* Check whether the screen size changed */ + size_change(0); +#endif + return code; +} + +/* + * Return a string which can be used in LYSystem() for spawning a subshell + */ +#if defined(__CYGWIN__) /* 1999/02/26 (Fri) */ +int Cygwin_Shell(void) +{ + char *shell; + int code; + STARTUPINFO startUpInfo; + PROCESS_INFORMATION procInfo; + SECURITY_ATTRIBUTES sa; + + /* Set up security attributes to allow inheritance of the file handle */ + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = 0; + sa.bInheritHandle = TRUE; + + /* Init a startup structure */ + GetStartupInfo(&startUpInfo); + + shell = LYGetEnv("COMSPEC"); + + /* Create the child process, specifying + inherited handles. Pass the value of the + handle as a command line parameter */ + code = 0; + if (shell) { + code = CreateProcess(0, shell, 0, 0, + TRUE, 0, + 0, 0, &startUpInfo, &procInfo); + + if (!code) { + printf("shell = [%s], code = %ld\n", shell, (long) GetLastError()); + } + + /* wait for the child to return (this is not a requirement + since the child is its own independent process) */ + WaitForSingleObject(procInfo.hProcess, INFINITE); + } + + return code; +} +#endif + +#ifdef WIN_EX +/* + * Quote the path to make it safe for shell command processing. + * We always quote it not only includes spaces in it. + * At least we should quote paths which include "&". + */ +char *quote_pathname(char *pathname) +{ + char *result = NULL; + + HTSprintf0(&result, "\"%s\"", pathname); + return result; +} +#endif + +const char *LYSysShell(void) +{ + const char *shell = 0; + +#ifdef DOSPATH +#ifdef WIN_EX + shell = LYGetEnv("SHELL"); + if (shell) { + if (access(shell, 0) != 0) + shell = LYGetEnv("COMSPEC"); + } else { + shell = LYGetEnv("COMSPEC"); + } + if (shell == NULL) { + if (system_is_NT) + shell = "cmd.exe"; + else + shell = "command.com"; + } +#else + shell = LYGetEnv("SHELL"); + if (shell == NULL) { + shell = LYGetEnv("COMSPEC"); + } + if (shell == NULL) { + shell = "command.com"; + } +#endif /* WIN_EX */ +#else +#ifdef __EMX__ + if (LYGetEnv("SHELL") != NULL) { + shell = LYGetEnv("SHELL"); + } else { + shell = (LYGetEnv("COMSPEC") == NULL) ? "cmd.exe" : LYGetEnv("COMSPEC"); + } +#else +#ifdef VMS + shell = ""; +#else + shell = "exec $SHELL"; +#endif /* __EMX__ */ +#endif /* VMS */ +#endif /* DOSPATH */ + return shell; +} + +#ifdef VMS +#define DISPLAY "DECW$DISPLAY" +#else +#define DISPLAY "DISPLAY" +#endif /* VMS */ + +/* + * Return the X-Window $DISPLAY string if it is nonnull/nonempty + */ +char *LYgetXDisplay(void) +{ + return LYGetEnv(DISPLAY); +} + +/* + * Set the value of the X-Window $DISPLAY variable (yes it leaks memory, but + * that is putenv's fault). + */ +void LYsetXDisplay(char *new_display) +{ + if (new_display != 0) { +#ifdef VMS + LYUpperCase(new_display); + Define_VMSLogical(DISPLAY, new_display); +#else + static char *display_putenv_command; + + display_putenv_command = NULL; /* yes, this is a leak - cannot fix */ + HTSprintf0(&display_putenv_command, "DISPLAY=%s", new_display); + putenv(display_putenv_command); +#endif /* VMS */ + if ((new_display = LYgetXDisplay()) != 0) { + StrAllocCopy(x_display, new_display); + } + } +} + +#ifdef CAN_CUT_AND_PASTE +#ifdef __EMX__ + +static int proc_type = -1; +static PPIB pib; +static HAB hab; +static HMQ hmq; + +static void morph_PM(void) +{ + PTIB tib; + int first = 0; + + if (proc_type == -1) { + DosGetInfoBlocks(&tib, &pib); + proc_type = pib->pib_ultype; + first = 1; + } + if (pib->pib_ultype != 3) /* 2 is VIO */ + pib->pib_ultype = 3; /* 3 is PM */ + if (first) + hab = WinInitialize(0); + /* 64 messages if before OS/2 3.0, ignored otherwise */ + hmq = WinCreateMsgQueue(hab, 64); + WinCancelShutdown(hmq, 1); /* Do not inform us on shutdown */ +} + +static void unmorph_PM(void) +{ + WinDestroyMsgQueue(hmq); + pib->pib_ultype = proc_type; +} + +int size_clip(void) +{ + return 8192; +} + +/* Code partially stolen from FED editor. */ + +int put_clip(const char *s) +{ + int sz = strlen(s) + 1; + int ret = EOF, nl = 0; + char *pByte = 0, *s1 = s, c, *t; + + while ((c = *s1++)) { + if (c == '\r' && *s1 == '\n') + s1++; + else if (c == '\n') + nl++; + } + if (DosAllocSharedMem((PPVOID) & pByte, 0, sz + nl, + PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE | OBJ_GETTABLE)) + return ret; + + if (!nl) + memcpy(pByte, s, sz); + else { + t = pByte; + while ((c = *t++ = *s++)) + if (c == '\n' && (t == pByte + 1 || t[-2] != '\r')) + t[-1] = '\r', *t++ = '\n'; + } + + morph_PM(); + if (!hab) + goto fail; + + WinOpenClipbrd(hab); + WinEmptyClipbrd(hab); + if (WinSetClipbrdData(hab, (ULONG) pByte, CF_TEXT, CFI_POINTER)) + ret = 0; + WinCloseClipbrd(hab); + unmorph_PM(); + if (ret == 0) + return 0; + fail: + DosFreeMem((PPVOID) & pByte); + return EOF; +} + +static int clip_open; + +/* get_clip_grab() returns a pointer to the string in the system area. + get_clip_release() should be called ASAP after this. */ + +char *get_clip_grab(void) +{ + char *ClipData; + ULONG ulFormat; + int sz; + + morph_PM(); + if (!hab) + return 0; + if (clip_open) + get_clip_release(); + + WinQueryClipbrdFmtInfo(hab, CF_TEXT, &ulFormat); + if (ulFormat != CFI_POINTER) { + unmorph_PM(); + return 0; + } + WinOpenClipbrd(hab); + clip_open = 1; + ClipData = (char *) WinQueryClipbrdData(hab, CF_TEXT); + sz = strlen(ClipData); + if (!ClipData || !sz) { + get_clip_release(); + return 0; + } + return ClipData; +} + +void get_clip_release(void) +{ + if (!clip_open) + return; + WinCloseClipbrd(hab); + clip_open = 0; + unmorph_PM(); +} + +#elif defined(WIN_EX) /* 1997/10/16 (Thu) 20:13:28 */ + +int put_clip(const char *szBuffer) +{ + HANDLE hWnd; + HANDLE m_hLogData; + LPTSTR pLogData; + HANDLE hClip; + int len; + + if (szBuffer == NULL) + return EOF; + + len = (int) strlen(szBuffer); + if (len == 0) + return EOF; + else + len++; + + m_hLogData = GlobalAlloc(GHND, len); + if (m_hLogData == NULL) { + return EOF; + } + + hWnd = NULL; + if (!OpenClipboard(hWnd)) { + return EOF; + } + /* Remove the current Clipboard contents */ + if (!EmptyClipboard()) { + GlobalFree(m_hLogData); + return EOF; + } + + /* Lock the global memory while we write to it. */ + pLogData = (LPTSTR) GlobalLock(m_hLogData); + + lstrcpy((LPTSTR) pLogData, szBuffer); + GlobalUnlock(m_hLogData); + + /* If there were any lines at all then copy them to clipboard. */ + hClip = SetClipboardData(CF_TEXT, m_hLogData); + if (!hClip) { + /* If we couldn't clip the data then free the global handle. */ + GlobalFree(m_hLogData); + } + + CloseClipboard(); + return 0; +} + +static HANDLE m_hLogData; +static int m_locked; + +/* get_clip_grab() returns a pointer to the string in the system area. + get_clip_release() should be called ASAP after this. */ + +char *get_clip_grab() +{ + HANDLE hWnd; + LPTSTR pLogData; + + hWnd = NULL; + if (!OpenClipboard(hWnd)) { + return 0; + } + + m_hLogData = GetClipboardData(CF_TEXT); + + if (m_hLogData == NULL) { + CloseClipboard(); + m_locked = 0; + return 0; + } + pLogData = (LPTSTR) GlobalLock(m_hLogData); + + m_locked = 1; + return pLogData; +} + +void get_clip_release() +{ + if (!m_locked) + return; + GlobalUnlock(m_hLogData); + CloseClipboard(); + m_locked = 0; +} + +#elif defined(HAVE_POPEN) + +static FILE *paste_handle = 0; +static char *paste_buf = NULL; + +void get_clip_release(void) +{ + if (paste_handle != 0) + pclose(paste_handle); + if (paste_buf) + FREE(paste_buf); +} + +static int clip_grab(void) +{ + char *cmd = LYGetEnv("RL_PASTE_CMD"); + + if (paste_handle) + pclose(paste_handle); + if (!cmd) + return 0; + + paste_handle = popen(cmd, TXT_R); + if (!paste_handle) + return 0; + return 1; +} + +#define PASTE_BUFFER 1008 +#define CF_TEXT 0 /* Not used */ + +char *get_clip_grab(void) +{ + int len; + unsigned size = PASTE_BUFFER; + int off = 0; + + if (!clip_grab()) + return NULL; + if (!paste_handle) + return NULL; + if (paste_buf) + FREE(paste_buf); + paste_buf = typeMallocn(char, PASTE_BUFFER); + + while (1) { + len = (int) fread(paste_buf + off, + (size_t) 1, + (size_t) PASTE_BUFFER - 1, + paste_handle); + paste_buf[off + len] = '\0'; + if (len < PASTE_BUFFER - 1) + break; + if (StrChr(paste_buf + off, '\r') + || StrChr(paste_buf + off, '\n')) + break; + paste_buf = typeRealloc(char, paste_buf, size += PASTE_BUFFER - 1); + + off += len; + } + return paste_buf; +} + +int put_clip(const char *s) +{ + char *cmd = LYGetEnv("RL_CLCOPY_CMD"); + FILE *fh; + size_t l = strlen(s), res; + + if (!cmd) + return -1; + + fh = popen(cmd, TXT_W); + if (!fh) + return -1; + res = fwrite(s, (size_t) 1, l, fh); + if (pclose(fh) != 0 || res != l) + return -1; + return 0; +} + +#endif /* __EMX__ ... HAVE_POPEN */ +#endif /* CAN_CUT_AND_PASTE */ + +/* + * Sleep for a number of milli-sec. + */ +void LYmsec_delay(unsigned msec) +{ +#if defined(_WINDOWS) + Sleep(msec); + +#elif defined(HAVE_NAPMS) + napms((int) msec); + +#elif defined(DJGPP) || defined(HAVE_USLEEP) + usleep(1000 * msec); + +#else + struct timeval tv; + unsigned long usec = 1000UL * msec; + + tv.tv_sec = usec / 1000000UL; + tv.tv_usec = usec % 1000000UL; + select(0, NULL, NULL, NULL, &tv); +#endif +} + +#if defined(WIN_EX) + +#ifndef WSABASEERR +#define WSABASEERR 10000 +#endif + +#ifdef ENABLE_IPV6 +#define WSOCK_NAME "ws2_32" +#else +#define WSOCK_NAME "wsock32" +#endif + +/* + * Description: the windows32 version of perror() + * + * Returns: a pointer to a static error + * + * Notes/Dependencies: I got this from + * comp.os.ms-windows.programmer.win32 + */ +char *w32_strerror(DWORD ercode) +{ +/* __declspec(thread) necessary if you will use multiple threads */ +#if defined(__CYGWIN__) || defined(__MINGW32__) + static char msg_buff[256]; + +#else + __declspec(thread) static char msg_buff[256]; +#endif + HMODULE hModule; + int i, msg_type; + unsigned char *p, *q, tmp_buff[256]; + DWORD rc; + + hModule = NULL; + msg_type = FORMAT_MESSAGE_FROM_SYSTEM; + /* Fill message buffer with a default message in + * case FormatMessage fails + */ + wsprintf(msg_buff, "Error %ld", ercode); + + /* + * Special code for winsock error handling. + */ + if (ercode > WSABASEERR) { + hModule = GetModuleHandle(WSOCK_NAME); + if (hModule) + msg_type = FORMAT_MESSAGE_FROM_HMODULE; + } + /* + * message handling. If not found in module, retry from system. + */ + rc = FormatMessage(msg_type, hModule, ercode, LANG_NEUTRAL, + msg_buff, sizeof(msg_buff), NULL); + + if (rc == 0 && msg_type == FORMAT_MESSAGE_FROM_HMODULE) { + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ercode, + LANG_NEUTRAL, msg_buff, sizeof(msg_buff), NULL); + } + + strcpy((char *) tmp_buff, msg_buff); + p = q = tmp_buff; + i = 0; + while (*p) { + if (!(*p == '\n' || *p == '\r')) + msg_buff[i++] = *p; + p++; + } + msg_buff[i] = '\0'; + + return msg_buff; +} + +#endif + +#if defined(SYSLOG_REQUESTED_URLS) +/* + * syslog() interface + */ +void LYOpenlog(const char *banner) +{ + if (syslog_requested_urls) { + CTRACE((tfp, "LYOpenlog(%s)\n", NONNULL(banner))); +#if defined(DJGPP) + openlog("lynx", LOG_PID | LOG_NDELAY, LOG_LOCAL5); +#else + openlog("lynx", LOG_PID, LOG_LOCAL5); +#endif + + if (banner) { + syslog(LOG_INFO, "Session start:%s", banner); + } else { + syslog(LOG_INFO, "Session start"); + } + } +} + +static BOOLEAN looks_like_password(char *first, + char *last) +{ + BOOLEAN result = FALSE; + + while (first <= last) { + if (*first == '/' + || *first == ':') { + result = FALSE; + break; + } + result = TRUE; + first++; + } + return result; +} + +void LYSyslog(char *arg) +{ + char *colon1; + char *colon2; + char *atsign; + + if (syslog_requested_urls) { + + CTRACE((tfp, "LYSyslog %s\n", arg)); + + if (is_url(arg)) { /* proto://user:password@host/path:port */ + /* ^this colon */ + if ((colon1 = StrChr(arg, ':')) != 0 + && !StrNCmp(colon1, "://", 3) + && (colon2 = StrChr(colon1 + 3, ':')) != 0 + && (atsign = StrChr(colon1, '@')) != 0 + && (colon2 < atsign) + && looks_like_password(colon2 + 1, atsign - 1)) { + char *buf = NULL; + + StrAllocCopy(buf, arg); + buf[colon2 - arg + 1] = 0; + StrAllocCat(buf, "******"); + StrAllocCat(buf, atsign); + syslog(LOG_INFO | LOG_LOCAL5, "%s", buf); + CTRACE((tfp, "...alter %s\n", buf)); + FREE(buf); + return; + } + } + syslog(LOG_INFO | LOG_LOCAL5, "%s", NONNULL(arg)); + } +} + +void LYCloselog(void) +{ + if (syslog_requested_urls) { + syslog(LOG_INFO, "Session over"); + closelog(); + } +} + +#endif /* SYSLOG_REQUESTED_URLS */ + +#if defined(WIN_EX) || defined(__CYGWIN__) /* 2000/03/07 (Tue) 17:17:46 */ + +#define IS_SEP(p) ((p == '\\') || (p == '/') || (p == ':')) + +static char *black_list[] = +{ + "con", + "prn", + "clock$", + "config$", + NULL +}; + +static int is_device(char *fname) +{ + HANDLE fileHandle; + DWORD val; + int i; + + i = 0; + while (black_list[i] != NULL) { + if (strcasecomp(fname, black_list[i]) == 0) { + return 1; /* device file */ + } + i++; + } + + fileHandle = CreateFile(fname, 0, 0, 0, OPEN_EXISTING, 0, 0); + + if (fileHandle == INVALID_HANDLE_VALUE) { + return 0; /* normal file */ + } else { + val = GetFileType(fileHandle); + switch (val) { + case 1: + val = 0; + break; + case 2: + val = 1; /* device file */ + break; + default: + val = 0; + break; + } + + CloseHandle(fileHandle); + } + return val; +} + +static char *device_list[] = +{ + "con", + "nul", + "aux", + "prn", + NULL +}; + +int unsafe_filename(const char *fname) +{ + int i, len, sum; + char *cp; + char *save; + + i = 0; + while (device_list[i] != NULL) { + if (strcasecomp(fname, device_list[i]) == 0) { + return 0; /* device file (open OK) */ + } + i++; + } + + save = cp = strdup(fname); + + while (*cp) { + if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp))) + cp += 2; /* KANJI skip */ + if (IS_SEP(*cp)) { + *cp = '\0'; + } + cp++; + } + + sum = 0; + cp = save; + len = (int) strlen(fname); + while (cp < (save + len)) { + if (*cp == '\0') { + cp++; + } else { + char *q; + + q = StrChr(cp, '.'); + if (q) + *q = '\0'; + if (is_device(cp)) { + sum++; + break; + } + if (q) + cp = q + 1; + while (*cp) + cp++; + } + } + free(save); + + return (sum != 0); +} + +FILE *safe_fopen(const char *fname, const char *mode) +{ + if (unsafe_filename(fname)) { + return (FILE *) NULL; + } else { + return fopen(fname, mode); + } +} + +#endif /* defined(WIN_EX) || defined(__CYGWIN__) */ |