1 /*
2  * $LynxId: LYUtils.c,v 1.242 2013/07/29 00:33:32 tom Exp $
3  */
4 #include <HTUtils.h>
5 #include <HTTCP.h>
6 #include <HTParse.h>
7 #include <HTAccess.h>
8 #include <HTCJK.h>
9 #include <HTAlert.h>
10 
11 #if defined(__MINGW32__)
12 
13 extern int kbhit(void);		/* FIXME: use conio.h */
14 
15 #undef UNIX
16 
17 #elif defined(_WINDOWS)
18 
19 #ifdef DONT_USE_GETTEXT
20 #undef gettext
21 #elif defined(HAVE_GETTEXT)
22 #undef gettext
23 #define gettext conio_gettext
24 #else
25 #undef gettext
26 #endif
27 
28 #include <conio.h>
29 
30 #ifdef DONT_USE_GETTEXT
31 #define gettext(s) s
32 #elif defined(HAVE_GETTEXT)
33 #undef gettext
34 #ifdef _INTL_REDIRECT_MACROS
35 #define gettext libintl_gettext	/* restore definition from libintl.h */
36 #endif
37 #else
38 #undef gettext
39 #define gettext(s) s
40 #endif
41 
42 #if !defined(kbhit) && defined(_WCONIO_DEFINED)
43 #define kbhit() _kbhit()	/* reasonably recent conio.h */
44 #endif
45 
46 #endif /* __MINGW32__ */
47 
48 #include <LYCurses.h>
49 #include <LYHistory.h>
50 #include <LYStrings.h>
51 #include <LYGlobalDefs.h>
52 #include <LYUtils.h>
53 #include <LYSignal.h>
54 #include <GridText.h>
55 #include <LYClean.h>
56 #include <LYCharSets.h>
57 #include <LYCharUtils.h>
58 
59 #include <LYMainLoop.h>
60 #include <LYKeymap.h>
61 
62 #ifdef __DJGPP__
63 #include <go32.h>
64 #include <sys/exceptn.h>
65 #endif /* __DJGPP__ */
66 
67 #ifndef NO_GROUPS
68 #include <HTFile.h>
69 #endif
70 
71 #ifdef _WINDOWS			/* 1998/04/30 (Thu) 19:04:25 */
72 #define GETPID()	(unsigned) (getpid() & 0xffff)
73 #else
74 #define GETPID()	(unsigned) getpid()
75 #endif /* _WINDOWS */
76 
77 #ifdef FNAMES_8_3
78 #define PID_FMT "%04x"
79 #else
80 #define PID_FMT "%u"
81 #endif
82 
83 #ifdef DJGPP_KEYHANDLER
84 #include <bios.h>
85 #endif /* DJGPP_KEYHANDLER */
86 
87 #ifdef __EMX__
88 #  define BOOLEAN OS2_BOOLEAN	/* Conflicts, but is used */
89 #  undef HT_ERROR		/* Conflicts too */
90 #  define INCL_PM		/* I want some PM functions.. */
91 #  define INCL_DOSPROCESS	/* TIB PIB. */
92 #  include <os2.h>
93 #  undef BOOLEAN
94 #endif
95 
96 #ifdef VMS
97 #include <descrip.h>
98 #include <libclidef.h>
99 #include <lib$routines.h>
100 #endif /* VMS */
101 
102 #ifdef HAVE_UTMP
103 #include <pwd.h>
104 #ifdef UTMPX_FOR_UTMP
105 #include <utmpx.h>
106 #define utmp utmpx
107 #ifdef UTMPX_FILE
108 #ifdef UTMP_FILE
109 #undef UTMP_FILE
110 #endif /* UTMP_FILE */
111 #define UTMP_FILE UTMPX_FILE
112 #else
113 #ifdef __UTMPX_FILE
114 #define UTMP_FILE __UTMPX_FILE	/* at least in OS/390  S/390 -- gil -- 2100 */
115 #else
116 #ifndef UTMP_FILE
117 #define UTMP_FILE "/var/adm/utmpx"	/* Digital Unix 4.0 */
118 #endif
119 #endif
120 #endif /* UTMPX_FILE */
121 #else
122 #include <utmp.h>
123 #endif /* UTMPX_FOR_UTMP */
124 #endif /* HAVE_UTMP */
125 
126 #ifdef NEED_PTEM_H
127 /* they neglected to define struct winsize in termios.h -- it's only in
128  * termio.h and ptem.h (the former conflicts with other definitions).
129  */
130 #include	<sys/stream.h>
131 #include	<sys/ptem.h>
132 #endif
133 
134 #include <LYLeaks.h>
135 
136 #ifdef USE_COLOR_STYLE
137 #include <AttrList.h>
138 #include <LYHash.h>
139 #include <LYStyle.h>
140 #endif
141 
142 #ifdef SVR4_BSDSELECT
143 extern int BSDselect(int nfds, fd_set * readfds, fd_set * writefds,
144 		     fd_set * exceptfds, struct timeval *timeout);
145 
146 #ifdef select
147 #undef select
148 #endif /* select */
149 #define select BSDselect
150 #ifdef SOCKS
151 #ifdef Rselect
152 #undef Rselect
153 #endif /* Rselect */
154 #define Rselect BSDselect
155 #endif /* SOCKS */
156 #endif /* SVR4_BSDSELECT */
157 
158 #ifdef __DJGPP__
159 #undef select			/* defined to select_s in www_tcp.h */
160 #endif
161 
162 #ifndef UTMP_FILE
163 #if defined(__FreeBSD__) || defined(__bsdi__)
164 #define UTMP_FILE _PATH_UTMP
165 #else
166 #define UTMP_FILE "/etc/utmp"
167 #endif /* __FreeBSD__ || __bsdi__ */
168 #endif /* !UTMP_FILE */
169 
170 /*
171  * experimental - make temporary filenames random to make the scheme less
172  * obvious.  However, as noted by KW, there are instances (such as the
173  * 'O'ption page, for which Lynx will store a temporary filename even when
174  * it no longer applies, since it will reuse that filename at a later time.
175  */
176 #ifdef USE_RAND_TEMPNAME
177 #if defined(LYNX_RAND_MAX)
178 #define HAVE_RAND_TEMPNAME 1
179 #define MAX_TEMPNAME 10000
180 #ifndef BITS_PER_CHAR
181 #define BITS_PER_CHAR 8
182 #endif
183 #endif
184 #endif
185 
186 #define COPY_COMMAND "%s %s %s"
187 
188 static HTList *localhost_aliases = NULL;	/* Hosts to treat as local */
189 static char *HomeDir = NULL;	/* HOME directory */
190 
191 HTList *sug_filenames = NULL;	/* Suggested filenames   */
192 
193 /*
194  * Maintain a list of all of the temp-files we create so that we can remove
195  * them during the cleanup.
196  */
197 typedef struct _LYTemp {
198     struct _LYTemp *next;
199     char *name;
200     BOOLEAN outs;
201     FILE *file;
202 } LY_TEMP;
203 
204 static LY_TEMP *ly_temp;
205 
FindTempfileByName(const char * name)206 static LY_TEMP *FindTempfileByName(const char *name)
207 {
208     LY_TEMP *p;
209 
210     for (p = ly_temp; p != 0; p = p->next) {
211 	if (!strcmp(p->name, name)) {
212 	    break;
213 	}
214     }
215     return p;
216 }
217 
FindTempfileByFP(FILE * fp)218 static LY_TEMP *FindTempfileByFP(FILE *fp)
219 {
220     LY_TEMP *p;
221 
222     for (p = ly_temp; p != 0; p = p->next) {
223 	if (p->file == fp) {
224 	    break;
225 	}
226     }
227     return p;
228 }
229 
230 #if defined(_WIN32)
231 /*
232  * Use RegQueryValueExA() rather than RegQueryValueEx() for compatibility
233  * with non-Unicode winvile
234  */
w32_get_reg_sz(HKEY hkey,const char * name,char * value,unsigned length)235 static int w32_get_reg_sz(HKEY hkey, const char *name, char *value, unsigned length)
236 {
237     int result;
238     DWORD dwSzBuffer = length;
239 
240     CTRACE((tfp, "w32_get_reg_sz(%s)\n", name));
241     result = RegQueryValueExA(hkey,
242 			      name,
243 			      NULL,
244 			      NULL,
245 			      (LPBYTE) value,
246 			      &dwSzBuffer);
247     if (result == ERROR_SUCCESS) {
248 	value[dwSzBuffer] = 0;
249 	CTRACE((tfp, "->%s\n", value));
250     }
251     return result;
252 }
253 #endif
254 
255 /*
256  * Get an environment variable, rejecting empty strings
257  */
LYGetEnv(const char * name)258 char *LYGetEnv(const char *name)
259 {
260     char *result = getenv(name);
261 
262 #if defined(_WIN32)
263     if (result == 0) {
264 	static HKEY rootkeys[] =
265 	{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
266 
267 	int j;
268 	HKEY hkey;
269 	char buffer[256];
270 
271 	for (j = 0; j < (int) TABLESIZE(rootkeys); ++j) {
272 	    if (RegOpenKeyEx(rootkeys[j],
273 			     LYNX_SUBKEY W32_STRING("\\Environment"),
274 			     0,
275 			     KEY_READ,
276 			     &hkey) == ERROR_SUCCESS) {
277 		if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) {
278 
279 		    result = strdup(buffer);
280 		    (void) RegCloseKey(hkey);
281 		    break;
282 		}
283 
284 		(void) RegCloseKey(hkey);
285 	    }
286 	}
287     }
288 #endif
289     return non_empty(result) ? result : 0;
290 }
291 
292 /*
293  * ascii versions of locale sensitive functions needed because in
294  * Turkish locales tolower("I") is not "i". That's fatal for case
295  * sensitive operations with charset names, HTML tags etc.
296  */
297 #ifdef USE_ASCII_CTYPES
ascii_tolower(int i)298 int ascii_tolower(int i)
299 {
300     if (91 > i && i > 64)
301 	return (i + 32);
302     else
303 	return i;
304 }
305 
ascii_toupper(int i)306 int ascii_toupper(int i)
307 {
308     if (123 > i && i > 96)
309 	return (i - 32);
310     else
311 	return i;
312 }
313 
ascii_isupper(int i)314 int ascii_isupper(int i)
315 {
316     if (91 > i && i > 64)
317 	return 1;
318     else
319 	return 0;
320 }
321 #endif /* USE_ASCII_CTYPES */
322 
323 /*
324  * Check for UTF-8 data, returning the length past the first character.
325  * Return zero if we found an ordinary character rather than UTF-8.
326  */
utf8_length(int utf_flag,const char * data)327 size_t utf8_length(int utf_flag,
328 		   const char *data)
329 {
330     size_t utf_extra = 0;
331 
332     if (utf_flag && is8bits(*data)) {
333 	if ((*data & 0xe0) == 0xc0) {
334 	    utf_extra = 1;
335 	} else if ((*data & 0xf0) == 0xe0) {
336 	    utf_extra = 2;
337 	} else if ((*data & 0xf8) == 0xf0) {
338 	    utf_extra = 3;
339 	} else if ((*data & 0xfc) == 0xf8) {
340 	    utf_extra = 4;
341 	} else if ((*data & 0xfe) == 0xfc) {
342 	    utf_extra = 5;
343 	} else {
344 	    /*
345 	     * Garbage.
346 	     */
347 	    utf_extra = 0;
348 	}
349 	if (strlen(data + 1) < utf_extra) {
350 	    /*
351 	     * Shouldn't happen.
352 	     */
353 	    utf_extra = 0;
354 	}
355     }
356     return utf_extra;
357 }
358 
359 /*
360  * Free storage used for the link-highlighting.
361  */
LYFreeHilites(int first,int last)362 void LYFreeHilites(int first, int last)
363 {
364     int i;
365 
366     for (i = first; i < last; i++) {
367 	LYSetHilite(i, NULL);
368 	FREE(links[i].lname);
369     }
370 }
371 
372 #define LXP (links[cur].lx)
373 #define LYP (links[cur].ly)
374 
375 /*
376  * Set the initial highlight information for a given link.
377  */
LYSetHilite(int cur,const char * text)378 void LYSetHilite(int cur,
379 		 const char *text)
380 {
381     links[cur].list.hl_base.hl_text = (char *) text;
382     links[cur].list.hl_len = (short) ((text != NULL) ? 1 : 0);
383     FREE(links[cur].list.hl_info);
384 }
385 
386 /*
387  * Add highlight information for the next line of a link.
388  */
LYAddHilite(int cur,char * text,int x)389 void LYAddHilite(int cur,
390 		 char *text,
391 		 int x)
392 {
393     HiliteList *list = &(links[cur].list);
394     HiliteInfo *have = list->hl_info;
395     size_t need = (unsigned) (list->hl_len - 1);
396     size_t want;
397 
398     list->hl_len = (short) (list->hl_len + 1);
399     want = (size_t) list->hl_len;
400 
401     if (have != NULL) {
402 	have = typeRealloc(HiliteInfo, have, want);
403     } else {
404 	have = typeMallocn(HiliteInfo, want);
405     }
406     list->hl_info = have;
407     have[need].hl_text = text;
408     have[need].hl_x = (short) x;
409 }
410 
411 /*
412  * Get the highlight text, counting from zero.
413  */
LYGetHiliteStr(int cur,int count)414 const char *LYGetHiliteStr(int cur,
415 			   int count)
416 {
417     const char *result;
418 
419     if (count >= links[cur].list.hl_len)
420 	result = NULL;
421     else if (count > 0)
422 	result = links[cur].list.hl_info[count - 1].hl_text;
423     else
424 	result = links[cur].list.hl_base.hl_text;
425     return result;
426 }
427 
428 /*
429  * Get the X-ordinate at which to draw the corresponding highlight-text
430  */
LYGetHilitePos(int cur,int count)431 int LYGetHilitePos(int cur,
432 		   int count)
433 {
434     int result;
435 
436     if (count >= links[cur].list.hl_len)
437 	result = -1;
438     else if (count > 0)
439 	result = links[cur].list.hl_info[count - 1].hl_x;
440     else
441 	result = LXP;
442     return result;
443 }
444 
445 #ifdef SHOW_WHEREIS_TARGETS
446 
447 #define SKIP_GLYPHS(theFlag, theData, theOffset) \
448 	(theFlag \
449 	    ? LYmbcs_skip_glyphs(theData, (theOffset), theFlag) \
450 	    : (theData + (theOffset)))
451 
452 /*
453  * If we have an emphasized WHEREIS hit in the highlighted text, restore the
454  * emphasis.  Note that we never emphasize the first and last characters of the
455  * highlighted text when we are making the link current, so the link attributes
456  * for the current link will persist at the beginning and end, providing an
457  * indication to the user that it has been made current.  Also note that we use
458  * HText_getFirstTargetInLine() to determine if there's a hit in the HText
459  * structure line containing the link, and if so, get back a copy of the line
460  * starting at that first hit (which might be before or after our link), and
461  * with all IsSpecial characters stripped, so we don't need to deal with them
462  * here.  -FM
463  */
show_whereis_targets(int flag,int cur,int count,const char * target,int TargetEmphasisON,int utf_flag)464 static BOOL show_whereis_targets(int flag,
465 				 int cur,
466 				 int count,
467 				 const char *target,
468 				 int TargetEmphasisON,
469 				 int utf_flag)
470 {
471     const char *Data = NULL;
472     const char *cp;
473     char *theData = NULL;
474     char buffer[MAX_LINE];
475     char tmp[7];
476     int HitOffset;
477     int LenNeeded;
478     int Offset;
479     int tLen;
480 
481     tmp[0] = tmp[1] = tmp[2] = '\0';
482 
483     if (non_empty(target)
484 	&& (links[cur].type & WWW_LINK_TYPE)
485 	&& non_empty(LYGetHiliteStr(cur, count))
486 	&& LYP + count < display_lines
487 	&& HText_getFirstTargetInLine(HTMainText,
488 				      links[cur].anchor_line_num + count,
489 				      utf_flag,
490 				      &Offset,
491 				      &tLen,
492 				      &theData,
493 				      target)) {
494 	int itmp, written, len, y, offset;
495 	const char *data;
496 	int tlen = (int) strlen(target);
497 	int hlen, hLen;
498 	int hLine = LYP + count;
499 	int hoffset = LYGetHilitePos(cur, count);
500 	size_t utf_extra = 0;
501 
502 	/*
503 	 * Copy into the buffer only what will fit up to the right border of
504 	 * the screen.  -FM
505 	 */
506 	LYmbcsstrncpy(buffer,
507 		      NonNull(LYGetHiliteStr(cur, count)),
508 		      (int) (sizeof(buffer) - 1),
509 		      (LYcolLimit - LYGetHilitePos(cur, count)),
510 		      utf_flag);
511 	hlen = (int) strlen(buffer);
512 	hLen = ((IS_CJK_TTY || utf_flag) ?
513 		LYmbcsstrlen(buffer, utf_flag, YES) : hlen);
514 
515 	/*
516 	 * Break out if the first hit in the line starts after this link.  -FM
517 	 */
518 	if (Offset < (hoffset + hLen)) {
519 	    /*
520 	     * Recursively skip hits that end before this link, and break out
521 	     * if there is no hit beyond those.  -FM
522 	     */
523 	    Data = theData;
524 	    while ((Offset < hoffset) &&
525 		   ((Offset + tLen) <= hoffset)) {
526 		data = (Data + tlen);
527 		offset = (Offset + tLen);
528 		if (((cp = LYno_attr_mb_strstr(data,
529 					       target,
530 					       utf_flag, YES,
531 					       &HitOffset,
532 					       &LenNeeded)) != NULL)
533 		    && (offset + LenNeeded) < LYcols) {
534 		    Data = cp;
535 		    Offset = (offset + HitOffset);
536 		} else {
537 		    goto highlight_search_done;
538 		}
539 	    }
540 	    data = buffer;
541 	    offset = hoffset;
542 
543 	    /*
544 	     * If the hit starts before the hightext, and ends in or beyond the
545 	     * hightext, restore the emphasis, skipping the first and last
546 	     * characters of the hightext if we're making the link current.
547 	     * -FM
548 	     */
549 	    if (offset >= 0 &&
550 		(Offset < offset) &&
551 		((Offset + tLen) > offset)) {
552 		itmp = 0;
553 		written = 0;
554 		len = (tlen - (offset - Offset));
555 
556 		/*
557 		 * Go to the start of the hightext and handle its first
558 		 * character.  -FM
559 		 */
560 		LYmove(hLine, offset);
561 		tmp[0] = data[itmp];
562 		utf_extra = utf8_length(utf_flag, data + itmp);
563 		if (utf_extra) {
564 		    LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
565 		    itmp += (int) utf_extra;
566 		    /*
567 		     * Start emphasis immediately if we are making the link
568 		     * non-current.  -FM
569 		     */
570 		    if (flag != TRUE) {
571 			LYstartTargetEmphasis();
572 			TargetEmphasisON = TRUE;
573 			LYaddstr(tmp);
574 		    } else {
575 			LYmove(hLine, (offset + 1));
576 		    }
577 		    tmp[1] = '\0';
578 		    written += (int) (utf_extra + 1);
579 		} else if (IS_CJK_TTY && is8bits(tmp[0])) {
580 		    /*
581 		     * For CJK strings, by Masanobu Kimura.
582 		     */
583 		    tmp[1] = data[++itmp];
584 		    /*
585 		     * Start emphasis immediately if we are making the link
586 		     * non-current.  -FM
587 		     */
588 		    if (flag != TRUE) {
589 			LYstartTargetEmphasis();
590 			TargetEmphasisON = TRUE;
591 			LYaddstr(tmp);
592 		    } else {
593 			LYmove(hLine, (offset + 1));
594 		    }
595 		    tmp[1] = '\0';
596 		    written += 2;
597 		} else {
598 		    /*
599 		     * Start emphasis immediately if we are making the link
600 		     * non-current.  -FM
601 		     */
602 		    if (flag != TRUE) {
603 			LYstartTargetEmphasis();
604 			TargetEmphasisON = TRUE;
605 			LYaddstr(tmp);
606 		    } else {
607 			LYmove(hLine, (offset + 1));
608 		    }
609 		    written++;
610 		}
611 		itmp++;
612 		/*
613 		 * Start emphasis after the first character if we are making
614 		 * the link current and this is not the last character.  -FM
615 		 */
616 		if (!TargetEmphasisON &&
617 		    data[itmp] != '\0') {
618 		    LYstartTargetEmphasis();
619 		    TargetEmphasisON = TRUE;
620 		}
621 
622 		/*
623 		 * Handle the remaining characters.  -FM
624 		 */
625 		for (;
626 		     written < len && (tmp[0] = data[itmp]) != '\0';
627 		     itmp++) {
628 		    /*
629 		     * Print all the other target chars, except the last
630 		     * character if it is also the last character of hightext
631 		     * and we are making the link current.  -FM
632 		     */
633 		    utf_extra = utf8_length(utf_flag, data + itmp);
634 		    if (utf_extra) {
635 			LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
636 			itmp += (int) utf_extra;
637 			/*
638 			 * Make sure we don't restore emphasis to the last
639 			 * character of hightext if we are making the link
640 			 * current.  -FM
641 			 */
642 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
643 			    LYstopTargetEmphasis();
644 			    TargetEmphasisON = FALSE;
645 			    LYGetYX(y, offset);
646 			    (void) y;
647 			    LYmove(hLine, (offset + 1));
648 			} else {
649 			    LYaddstr(tmp);
650 			}
651 			tmp[1] = '\0';
652 			written += (int) (utf_extra + 1);
653 		    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
654 			/*
655 			 * For CJK strings, by Masanobu Kimura.
656 			 */
657 			tmp[1] = data[++itmp];
658 			/*
659 			 * Make sure we don't restore emphasis to the last
660 			 * character of hightext if we are making the link
661 			 * current.  -FM
662 			 */
663 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
664 			    LYstopTargetEmphasis();
665 			    TargetEmphasisON = FALSE;
666 			    LYGetYX(y, offset);
667 			    LYmove(hLine, (offset + 1));
668 			} else {
669 			    LYaddstr(tmp);
670 			}
671 			tmp[1] = '\0';
672 			written += 2;
673 		    } else {
674 			/*
675 			 * Make sure we don't restore emphasis to the last
676 			 * character of hightext if we are making the link
677 			 * current.  -FM
678 			 */
679 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
680 			    LYstopTargetEmphasis();
681 			    TargetEmphasisON = FALSE;
682 			    LYGetYX(y, offset);
683 			    LYmove(hLine, (offset + 1));
684 			} else {
685 			    LYaddstr(tmp);
686 			}
687 			written++;
688 		    }
689 		}
690 
691 		/*
692 		 * Stop the emphasis if we haven't already, then reset the
693 		 * offset to our current position in the line, and if that is
694 		 * beyond the link, or or we are making the link current and it
695 		 * is the last character of the hightext, we are done.  -FM
696 		 */
697 		if (TargetEmphasisON) {
698 		    LYstopTargetEmphasis();
699 		    TargetEmphasisON = FALSE;
700 		}
701 		LYGetYX(y, offset);
702 		if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
703 		/*
704 		 * See if we have another hit that starts within the
705 		 * hightext.  -FM
706 		 */
707 		    && ((cp =
708 			 LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
709 								Data,
710 								offset - Offset),
711 					     target,
712 					     utf_flag, YES,
713 					     &HitOffset,
714 					     &LenNeeded)) != NULL)
715 		    && (offset + LenNeeded) < LYcols
716 		/*
717 		 * If the hit starts after the end of the hightext, or we
718 		 * are making the link current and the hit starts at its
719 		 * last character, we are done.  -FM
720 		 */
721 		    && (HitOffset + offset) <
722 		    (hoffset +
723 		     (flag == TRUE ? (hLen - 1) : hLen))) {
724 		    /*
725 		     * Set up the data and offset for the hit, and let the code
726 		     * for within hightext hits handle it.  -FM
727 		     */
728 		    Data = cp;
729 		    Offset = (offset + HitOffset);
730 		    data = buffer;
731 		    offset = hoffset;
732 		    goto highlight_hit_within_hightext;
733 		}
734 		goto highlight_search_done;
735 	    }
736 
737 	  highlight_hit_within_hightext:
738 	    /*
739 	     * If we get to here, the hit starts within the hightext.  If we
740 	     * are making the link current and it's the last character in the
741 	     * hightext, we are done.  Otherwise, move there and start
742 	     * restoring the emphasis.  -FM
743 	     */
744 	    if ((Offset - offset) <= (flag == TRUE ? (hLen - 1) : hLen)) {
745 		data = SKIP_GLYPHS(utf_flag, data, Offset - offset);
746 		if (utf_flag) {
747 		    LYrefresh();
748 		}
749 		offset = Offset;
750 		itmp = 0;
751 		written = 0;
752 		len = tlen;
753 
754 		/*
755 		 * Go to the start of the hit and handle its first character.
756 		 * -FM
757 		 */
758 		LYmove(hLine, offset);
759 		tmp[0] = data[itmp];
760 		utf_extra = utf8_length(utf_flag, data + itmp);
761 		if (utf_extra) {
762 		    LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
763 		    itmp += (int) utf_extra;
764 		    /*
765 		     * Start emphasis immediately if we are making the link
766 		     * non-current, or we are making it current but this is not
767 		     * the first or last character of the hightext.  -FM
768 		     */
769 		    if (flag != TRUE ||
770 			(offset > hoffset && data[itmp + 1] != '\0')) {
771 			LYstartTargetEmphasis();
772 			TargetEmphasisON = TRUE;
773 			LYaddstr(tmp);
774 		    } else {
775 			LYmove(hLine, (offset + 1));
776 		    }
777 		    tmp[1] = '\0';
778 		    written += (int) (utf_extra + 1);
779 		} else if (IS_CJK_TTY && is8bits(tmp[0])) {
780 		    /*
781 		     * For CJK strings, by Masanobu Kimura.
782 		     */
783 		    tmp[1] = data[++itmp];
784 		    /*
785 		     * Start emphasis immediately if we are making the link
786 		     * non-current, or we are making it current but this is not
787 		     * the first or last character of the hightext.  -FM
788 		     */
789 		    if (flag != TRUE ||
790 			(offset > hoffset && data[itmp + 1] != '\0')) {
791 			LYstartTargetEmphasis();
792 			TargetEmphasisON = TRUE;
793 			LYaddstr(tmp);
794 		    } else {
795 			LYmove(hLine, (offset + 2));
796 		    }
797 		    tmp[1] = '\0';
798 		    written += 2;
799 		} else {
800 		    /*
801 		     * Start emphasis immediately if we are making the link
802 		     * non-current, or we are making it current but this is not
803 		     * the first or last character of the hightext.  -FM
804 		     */
805 		    if (flag != TRUE ||
806 			(offset > hoffset && data[itmp + 1] != '\0')) {
807 			LYstartTargetEmphasis();
808 			TargetEmphasisON = TRUE;
809 			LYaddstr(tmp);
810 		    } else {
811 			LYmove(hLine, (offset + 1));
812 		    }
813 		    written++;
814 		}
815 		itmp++;
816 		/*
817 		 * Start emphasis after the first character if we are making
818 		 * the link current and this is not the last character.  -FM
819 		 */
820 		if (!TargetEmphasisON &&
821 		    data[itmp] != '\0') {
822 		    LYstartTargetEmphasis();
823 		    TargetEmphasisON = TRUE;
824 		}
825 
826 		for (;
827 		     written < len && (tmp[0] = data[itmp]) != '\0';
828 		     itmp++) {
829 		    /*
830 		     * Print all the other target chars, except the last
831 		     * character if it is also the last character of hightext
832 		     * and we are making the link current.  -FM
833 		     */
834 		    utf_extra = utf8_length(utf_flag, data + itmp);
835 		    if (utf_extra) {
836 			LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
837 			itmp += (int) utf_extra;
838 			/*
839 			 * Make sure we don't restore emphasis to the last
840 			 * character of hightext if we are making the link
841 			 * current.  -FM
842 			 */
843 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
844 			    LYstopTargetEmphasis();
845 			    TargetEmphasisON = FALSE;
846 			    LYGetYX(y, offset);
847 			    LYmove(hLine, (offset + 1));
848 			} else {
849 			    LYaddstr(tmp);
850 			}
851 			tmp[1] = '\0';
852 			written += (int) (utf_extra + 1);
853 		    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
854 			/*
855 			 * For CJK strings, by Masanobu Kimura.
856 			 */
857 			tmp[1] = data[++itmp];
858 			/*
859 			 * Make sure we don't restore emphasis to the last
860 			 * character of hightext if we are making the link
861 			 * current.  -FM
862 			 */
863 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
864 			    LYstopTargetEmphasis();
865 			    TargetEmphasisON = FALSE;
866 			    LYGetYX(y, offset);
867 			    LYmove(hLine, (offset + 1));
868 			} else {
869 			    LYaddstr(tmp);
870 			}
871 			tmp[1] = '\0';
872 			written += 2;
873 		    } else {
874 			/*
875 			 * Make sure we don't restore emphasis to the last
876 			 * character of hightext if we are making the link
877 			 * current.  -FM
878 			 */
879 			if (flag == TRUE && data[(itmp + 1)] == '\0') {
880 			    LYstopTargetEmphasis();
881 			    TargetEmphasisON = FALSE;
882 			    LYGetYX(y, offset);
883 			    LYmove(hLine, (offset + 1));
884 			} else {
885 			    LYaddstr(tmp);
886 			}
887 			written++;
888 		    }
889 		}
890 
891 		/*
892 		 * Stop the emphasis if we haven't already, then reset the
893 		 * offset to our current position in the line, and if that is
894 		 * beyond the link, or we are making the link current and it is
895 		 * the last character in the hightext, we are done.  -FM
896 		 */
897 		if (TargetEmphasisON) {
898 		    LYstopTargetEmphasis();
899 		    TargetEmphasisON = FALSE;
900 		}
901 		LYGetYX(y, offset);
902 		if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
903 		/*
904 		 * See if we have another hit that starts within the
905 		 * hightext.  -FM
906 		 */
907 		    && ((cp =
908 			 LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
909 								Data,
910 								offset - Offset),
911 					     target,
912 					     utf_flag, YES,
913 					     &HitOffset,
914 					     &LenNeeded)) != NULL)
915 		    && (offset + LenNeeded) < LYcols
916 		/*
917 		 * If the hit starts after the end of the hightext, or we
918 		 * are making the link current and the hit starts at its
919 		 * last character, we are done.  -FM
920 		 */
921 		    && (HitOffset + offset) <
922 		    (hoffset + (flag == TRUE ? (hLen - 1) : hLen))) {
923 		    /*
924 		     * If the target extends beyond our buffer, emphasize
925 		     * everything in the hightext starting at this hit.
926 		     * Otherwise, set up the data and offsets, and loop back.
927 		     * -FM
928 		     */
929 		    if ((HitOffset + (offset + tLen)) >= (hoffset + hLen)) {
930 			offset = (HitOffset + offset);
931 			data = SKIP_GLYPHS(utf_flag, Data, offset - hoffset);
932 			if (utf_flag) {
933 			    LYrefresh();
934 			}
935 			LYmove(hLine, offset);
936 			itmp = 0;
937 			written = 0;
938 			len = (int) strlen(data);
939 
940 			/*
941 			 * Turn the emphasis back on.  -FM
942 			 */
943 			LYstartTargetEmphasis();
944 			TargetEmphasisON = TRUE;
945 			for (;
946 			     written < len && (tmp[0] = data[itmp]) != '\0';
947 			     itmp++) {
948 			    /*
949 			     * Print all the other target chars, except the
950 			     * last character if it is also the last character
951 			     * of hightext and we are making the link current.
952 			     * -FM
953 			     */
954 			    utf_extra = utf8_length(utf_flag, data + itmp);
955 			    if (utf_extra) {
956 				LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
957 				itmp += (int) utf_extra;
958 				/*
959 				 * Make sure we don't restore emphasis to the
960 				 * last character of hightext if we are making
961 				 * the link current.  -FM
962 				 */
963 				if (flag == TRUE && data[(itmp + 1)] == '\0') {
964 				    LYstopTargetEmphasis();
965 				    TargetEmphasisON = FALSE;
966 				    LYGetYX(y, offset);
967 				    LYmove(hLine, (offset + 1));
968 				} else {
969 				    LYaddstr(tmp);
970 				}
971 				tmp[1] = '\0';
972 				written += (int) (utf_extra + 1);
973 			    } else if (IS_CJK_TTY && is8bits(tmp[0])) {
974 				/*
975 				 * For CJK strings, by Masanobu Kimura.
976 				 */
977 				tmp[1] = data[++itmp];
978 				/*
979 				 * Make sure we don't restore emphasis to the
980 				 * last character of hightext if we are making
981 				 * the link current.  -FM
982 				 */
983 				if (flag == TRUE && data[(itmp + 1)] == '\0') {
984 				    LYstopTargetEmphasis();
985 				    TargetEmphasisON = FALSE;
986 				} else {
987 				    LYaddstr(tmp);
988 				}
989 				tmp[1] = '\0';
990 				written += 2;
991 			    } else {
992 				/*
993 				 * Make sure we don't restore emphasis to the
994 				 * last character of hightext if we are making
995 				 * the link current.  -FM
996 				 */
997 				if (flag == TRUE && data[(itmp + 1)] == '\0') {
998 				    LYstopTargetEmphasis();
999 				    TargetEmphasisON = FALSE;
1000 				} else {
1001 				    LYaddstr(tmp);
1002 				}
1003 				written++;
1004 			    }
1005 			}
1006 			/*
1007 			 * Turn off the emphasis if we haven't already, and
1008 			 * then we're done.  -FM
1009 			 */
1010 			if (TargetEmphasisON) {
1011 			    LYstopTargetEmphasis();
1012 			}
1013 		    } else {
1014 			Data = cp;
1015 			Offset = (offset + HitOffset);
1016 			data = buffer;
1017 			offset = hoffset;
1018 			goto highlight_hit_within_hightext;
1019 		    }
1020 		}
1021 	    }
1022 	}
1023     }
1024   highlight_search_done:
1025     FREE(theData);
1026     return (BOOLEAN) TargetEmphasisON;
1027 }
1028 #endif /* SHOW_WHEREIS_TARGETS */
1029 
1030 #ifdef USE_COLOR_STYLE
find_cached_style(int cur,int flag)1031 static int find_cached_style(int cur,
1032 			     int flag)
1033 {
1034     int s = s_alink;
1035 
1036 #ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
1037     if (textfields_need_activation
1038 	&& links[cur].type == WWW_FORM_LINK_TYPE
1039 	&& F_TEXTLIKE(links[cur].l_form->type))
1040 	s = s_curedit;
1041 #endif
1042 
1043     if (flag != TRUE) {
1044 	int x;
1045 
1046 	/*
1047 	 * This is where we try to restore the original style when a link is
1048 	 * unhighlighted.  The cached styles array saves the original style
1049 	 * just for this case.  If it doesn't have a color change saved at just
1050 	 * the right position, we look at preceding positions in the same line
1051 	 * until we find one.
1052 	 */
1053 	if (ValidCachedStyle(LYP, LXP)) {
1054 	    CTRACE2(TRACE_STYLE,
1055 		    (tfp, "STYLE.highlight.off: cached style @(%d,%d): ",
1056 		     LYP, LXP));
1057 	    s = (int) GetCachedStyle(LYP, LXP);
1058 	    if (s == 0) {
1059 		for (x = LXP - 1; x >= 0; x--) {
1060 		    s = (int) GetCachedStyle(LYP, x);
1061 		    if (s != 0) {
1062 			SetCachedStyle(LYP, LXP, (unsigned) s);
1063 			CTRACE2(TRACE_STYLE,
1064 				(tfp, "found %d, x_offset=%d.\n", s, x - LXP));
1065 			break;
1066 		    }
1067 		}
1068 		if (s == 0) {
1069 		    CTRACE2(TRACE_STYLE, (tfp, "not found, assume <a>.\n"));
1070 		    s = s_a;
1071 		}
1072 	    } else {
1073 		CTRACE2(TRACE_STYLE, (tfp, "found %d.\n", s));
1074 	    }
1075 	} else {
1076 	    CTRACE2(TRACE_STYLE,
1077 		    (tfp, "STYLE.highlight.off: can't use cache.\n"));
1078 	    s = s_a;
1079 	}
1080     } else {
1081 	CTRACE2(TRACE_STYLE, (tfp, "STYLE.highlight.on: @(%d,%d).\n", LYP, LXP));
1082     }
1083     return s;
1084 }
1085 #endif /* USE_COLOR_STYLE */
1086 
1087 /*
1088  * Highlight (or unhighlight) a given link.
1089  */
LYhighlight(int flag,int cur,const char * target)1090 void LYhighlight(int flag,
1091 		 int cur,
1092 		 const char *target)
1093 {
1094     char buffer[MAX_LINE];
1095     int i;
1096     int hi_count;
1097     int hi_offset;
1098     int title_adjust = (no_title ? -TITLE_LINES : 0);
1099     char tmp[7];
1100     const char *hi_string;
1101 
1102 #ifdef SHOW_WHEREIS_TARGETS
1103     BOOL TargetEmphasisON = FALSE;
1104     BOOL target1_drawn = NO;
1105 #endif
1106     BOOL utf_flag = (BOOL) IS_UTF8_TTY;
1107     BOOL hl1_drawn = NO;
1108 
1109 #ifdef USE_COLOR_STYLE
1110     BOOL hl2_drawn = FALSE;	/* whether links[cur].l_hightext2 is already drawn
1111 
1112 				   properly */
1113 #endif
1114     tmp[0] = tmp[1] = tmp[2] = '\0';
1115 
1116     /*
1117      * Bugs in the history code might cause -1 to be sent for cur, which yields
1118      * a crash when LYStrNCpy() is called with a nonsense pointer.  As far as I
1119      * know, such bugs have been squashed, but if they should reappear, this
1120      * works around them.  -FM
1121      */
1122     if (cur < 0) {
1123 	CTRACE((tfp, "LYhighlight cur %d (bug workaround)\n", cur));
1124 	cur = 0;
1125     }
1126 
1127     CTRACE((tfp, "LYhighlight at(%2d,%2d) %s %d [%d]:%s\n",
1128 	    links[cur].ly, links[cur].lx,
1129 	    (flag
1130 	     ? "on"
1131 	     : "off"),
1132 	    cur,
1133 	    links[cur].anchor_number,
1134 	    NONNULL(target)));
1135 
1136 #if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
1137     if (flag == FALSE)
1138 	textinput_redrawn = FALSE;
1139 #endif
1140 
1141     if (nlinks > 0) {
1142 #ifdef USE_COLOR_STYLE
1143 	if (flag == TRUE || links[cur].type == WWW_FORM_LINK_TYPE) {
1144 	    LYmove(LYP + title_adjust, LXP);
1145 	    LynxChangeStyle(find_cached_style(cur, flag), STACK_ON);
1146 	}
1147 #else
1148 	if (links[cur].type == WWW_FORM_LINK_TYPE
1149 	    || LYGetHiliteStr(cur, 0) == NULL) {
1150 	    LYMoveToLink(cur, target, NULL,
1151 			 flag, links[cur].inUnderline, utf_flag);
1152 	    lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
1153 	} else {
1154 	    LYMoveToLink(cur, target, LYGetHiliteStr(cur, 0),
1155 			 flag, links[cur].inUnderline, utf_flag);
1156 	    hl1_drawn = YES;
1157 #ifdef SHOW_WHEREIS_TARGETS
1158 	    target1_drawn = YES;
1159 #endif
1160 	}
1161 #endif
1162 
1163 	if (links[cur].type == WWW_FORM_LINK_TYPE) {
1164 	    int len;
1165 	    int avail_space = (LYcolLimit - LXP) + (LYcolLimit * (LYlines - LYP));
1166 	    const char *text = LYGetHiliteStr(cur, 0);
1167 
1168 	    if (text == 0)
1169 		text = "";
1170 
1171 	    if (avail_space > links[cur].l_form->size)
1172 		avail_space = links[cur].l_form->size;
1173 
1174 	    len = (int) (LYmbcs_skip_cells(text, avail_space, utf_flag) - text);
1175 	    LYwaddnstr(LYwin, text, (size_t) len);
1176 	    while (len++ < avail_space)
1177 		LYaddch('_');
1178 
1179 #ifdef USE_COLOR_STYLE
1180 	} else if (flag == FALSE) {
1181 	    hl2_drawn = TRUE;
1182 	    redraw_lines_of_link(cur);
1183 	    CTRACE2(TRACE_STYLE,
1184 		    (tfp, "STYLE.highlight.off: NOFIX branch @(%d,%d).\n",
1185 		     LYP, LXP));
1186 #endif
1187 	} else if (!hl1_drawn) {
1188 	    /*
1189 	     * Copy into the buffer only what will fit within the width of the
1190 	     * screen.
1191 	     */
1192 	    LYmbcsstrncpy(buffer,
1193 			  NonNull(LYGetHiliteStr(cur, 0)),
1194 			  (int) (sizeof(buffer) - 1),
1195 			  (LYcolLimit - LXP),
1196 			  utf_flag);
1197 	    LYaddstr(buffer);
1198 	}
1199 
1200 	/*
1201 	 * Display a second line as well.
1202 	 */
1203 #ifdef USE_COLOR_STYLE
1204 	if (hl2_drawn == FALSE)
1205 #endif
1206 	{
1207 	    for (hi_count = 1;
1208 		 (hi_string = LYGetHiliteStr(cur, hi_count)) != NULL
1209 		 && LYP + hi_count <= display_lines;
1210 		 ++hi_count) {
1211 		int row = LYP + hi_count + title_adjust;
1212 
1213 		hi_offset = LYGetHilitePos(cur, hi_count);
1214 		if (hi_offset < 0)
1215 		    continue;
1216 		lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
1217 		LYmove(row, hi_offset);
1218 
1219 #ifdef USE_COLOR_STYLE
1220 		CTRACE2(TRACE_STYLE,
1221 			(tfp, "STYLE.highlight.line2: @(%d,%d), style=%d.\n",
1222 			 row, hi_offset,
1223 			 flag == TRUE ? s_alink : s_a));
1224 		LynxChangeStyle(flag == TRUE ? s_alink : s_a, ABS_ON);
1225 #else
1226 		lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
1227 #endif
1228 
1229 		for (i = 0; (tmp[0] = hi_string[i]) != '\0'
1230 		     && (i + hi_offset) < LYcols; i++) {
1231 		    if (!IsSpecialAttrChar(hi_string[i])) {
1232 			/*
1233 			 * For CJK strings, by Masanobu Kimura.
1234 			 */
1235 			if (IS_CJK_TTY && is8bits(tmp[0])) {
1236 			    tmp[1] = hi_string[++i];
1237 			    LYaddstr(tmp);
1238 			    tmp[1] = '\0';
1239 			} else {
1240 			    LYaddstr(tmp);
1241 			}
1242 		    }
1243 		}
1244 	    }
1245 	    lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
1246 	}
1247 #ifdef SHOW_WHEREIS_TARGETS
1248 	for (hi_count = target1_drawn ? 1 : 0;
1249 	     LYGetHiliteStr(cur, hi_count) != NULL;
1250 	     hi_count++) {
1251 	    TargetEmphasisON = show_whereis_targets(flag,
1252 						    cur,
1253 						    hi_count,
1254 						    target,
1255 						    TargetEmphasisON,
1256 						    utf_flag);
1257 	}
1258 
1259 	if (!LYShowCursor)
1260 	    /*
1261 	     * Get cursor out of the way.
1262 	     */
1263 	    LYHideCursor();
1264 	else
1265 #endif /* SHOW_WHEREIS_TARGETS */
1266 	    /*
1267 	     * Never hide the cursor if there's no FANCY CURSES or SLANG.
1268 	     */
1269 	    LYmove(LYP + title_adjust, ((LXP > 0) ? (LXP - 1) : 0));
1270 
1271 	if (flag)
1272 	    LYrefresh();
1273     }
1274     return;
1275 }
1276 
1277 /*
1278  * free_and_clear will free a pointer if it is non-zero and then set it to
1279  * zero.
1280  */
free_and_clear(char ** pointer)1281 void free_and_clear(char **pointer)
1282 {
1283     if (*pointer) {
1284 	FREE(*pointer);
1285 	*pointer = 0;
1286     }
1287     return;
1288 }
1289 
1290 /*
1291  * Convert single or serial newlines to single spaces throughout a string
1292  * (ignore newlines if the preceding character is a space) and convert tabs to
1293  * single spaces.  Don't ignore any explicit tabs or spaces if the condense
1294  * argument is FALSE, otherwise, condense any serial spaces or tabs to one
1295  * space.  - FM
1296  */
convert_to_spaces(char * string,int condense)1297 void convert_to_spaces(char *string,
1298 		       int condense)
1299 {
1300     char *s = string;
1301     char *ns;
1302     BOOL last_is_space = FALSE;
1303 
1304     if (!s)
1305 	return;
1306 
1307     s = LYSkipNonBlanks(s);
1308     ns = s;
1309 
1310     while (*s) {
1311 	switch (*s) {
1312 	case ' ':
1313 	case '\t':
1314 	    if (!(condense && last_is_space))
1315 		*(ns++) = ' ';
1316 	    last_is_space = TRUE;
1317 	    break;
1318 
1319 	case '\r':
1320 	case '\n':
1321 	    if (!last_is_space) {
1322 		*(ns++) = ' ';
1323 		last_is_space = TRUE;
1324 	    }
1325 	    break;
1326 
1327 	default:
1328 	    *(ns++) = *s;
1329 	    last_is_space = FALSE;
1330 	    break;
1331 	}
1332 	s++;
1333     }
1334     *ns = '\0';
1335     return;
1336 }
1337 
1338 /*
1339  * Strip trailing slashes from directory paths.
1340  */
strip_trailing_slash(char * dirname)1341 char *strip_trailing_slash(char *dirname)
1342 {
1343     int i;
1344 
1345     i = (int) strlen(dirname) - 1;
1346     while (i >= 0 && dirname[i] == '/')
1347 	dirname[i--] = '\0';
1348     return (dirname);
1349 }
1350 
1351 /*
1352  * Remove most blanks, but restore one trailing blank to make prompts nicer.
1353  */
remove_most_blanks(char * buffer)1354 static void remove_most_blanks(char *buffer)
1355 {
1356     int length = (int) strlen(buffer);
1357     BOOL trailing = (BOOL) ((length != 0) && (buffer[length - 1] == ' '));
1358 
1359     LYReduceBlanks(buffer);
1360     if (trailing)
1361 	strcat(buffer, " ");
1362 }
1363 
1364 /*
1365  * Display (or hide) the status line.
1366  */
1367 BOOLEAN mustshow = FALSE;
1368 
statusline(const char * text)1369 void statusline(const char *text)
1370 {
1371     char buffer[MAX_LINE];
1372     unsigned char *temp = NULL;
1373     int max_length, len, i, j;
1374     int at_lineno;
1375     unsigned char k;
1376     char *p;
1377     char text_buff[MAX_LINE];
1378 
1379     if (text == NULL)
1380 	return;
1381 
1382     /*
1383      * Don't print statusline messages if dumping to stdout.
1384      */
1385     if (dump_output_immediately)
1386 	return;
1387 
1388     /*
1389      * Don't print statusline message if turned off.
1390      */
1391     if (mustshow != TRUE) {
1392 	if (no_statusline == TRUE) {
1393 	    return;
1394 	}
1395     }
1396     mustshow = FALSE;
1397 
1398     /* "LYNXDOWNLOAD://Method=-1/File=%s/SugFile=%s%s\">Save to disk</a>\n" */
1399     LYStrNCpy(text_buff, text, sizeof(text_buff) - 1);
1400     p = strchr(text_buff, '\n');
1401     if (p)
1402 	*p = '\0';
1403 
1404     /*
1405      * Deal with any CJK escape sequences and Kanji if we have a CJK character
1406      * set selected, otherwise, strip any escapes.  Also, make sure text is not
1407      * longer than the statusline window.  - FM
1408      */
1409     max_length = (((LYcolLimit - 1) < (int) sizeof(buffer))
1410 		  ? (LYcolLimit - 1)
1411 		  : (int) sizeof(buffer) - 1);
1412     if ((text_buff[0] != '\0') &&
1413 	(LYHaveCJKCharacterSet)) {
1414 	/*
1415 	 * Translate or filter any escape sequences.  - FM
1416 	 */
1417 	if ((temp = typecallocn(unsigned char, strlen(text_buff) + 1)) == NULL)
1418 	      outofmem(__FILE__, "statusline");
1419 
1420 	assert(temp != NULL);
1421 
1422 	if (kanji_code == EUC) {
1423 	    TO_EUC((const unsigned char *) text_buff, temp);
1424 	} else if (kanji_code == SJIS) {
1425 #ifdef KANJI_CODE_OVERRIDE
1426 	    if (!LYRawMode || last_kcode == SJIS)
1427 		strcpy(temp, text_buff);
1428 	    else
1429 		TO_SJIS((const unsigned char *) text_buff, temp);
1430 #else
1431 	    strcpy((char *) temp, text_buff);
1432 #endif
1433 	} else {
1434 	    for (i = 0, j = 0; text_buff[i]; i++) {
1435 		if (text_buff[i] != CH_ESC) {	/* S/390 -- gil -- 2119 */
1436 		    temp[j++] = UCH(text_buff[i]);
1437 		}
1438 	    }
1439 	    temp[j] = '\0';
1440 	}
1441 
1442 	/*
1443 	 * Deal with any newlines or tabs in the string.  - FM
1444 	 */
1445 	remove_most_blanks((char *) temp);
1446 
1447 	/*
1448 	 * Handle the Kanji, making sure the text is not longer than the
1449 	 * statusline window.  - FM
1450 	 */
1451 	for (i = 0, j = 0, len = 0, k = '\0';
1452 	     temp[i] != '\0' && len < max_length; i++) {
1453 	    if (k != '\0') {
1454 		buffer[j++] = (char) k;
1455 		buffer[j++] = (char) temp[i];
1456 		k = '\0';
1457 		len += 2;
1458 	    } else if ((temp[i] & 0200) != 0) {
1459 		k = temp[i];
1460 	    } else {
1461 		buffer[j++] = (char) temp[i];
1462 		len++;
1463 	    }
1464 	}
1465 	buffer[j] = '\0';
1466 	FREE(temp);
1467     } else {
1468 	/*
1469 	 * Deal with any newlines or tabs in the string.  - FM
1470 	 */
1471 	remove_most_blanks(text_buff);
1472 #ifdef WIDEC_CURSES
1473 	len = (int) strlen(text_buff);
1474 	if (len >= (int) (sizeof(buffer) - 1))
1475 	    len = (int) (sizeof(buffer) - 1);
1476 	LYStrNCpy(buffer, text_buff, len);
1477 	/* FIXME: a binary search might be faster */
1478 	while (len > 0 && LYstrExtent(buffer, len, len) > max_length)
1479 	    buffer[--len] = '\0';
1480 #else
1481 	/*
1482 	 * Strip any escapes, and shorten text if necessary.  Note that we
1483 	 * don't deal with the possibility of UTF-8 characters in the string.
1484 	 * This is unlikely, but if strings with such characters are used in
1485 	 * LYMessages_en.h, a compilation symbol of HAVE_UTF8_STATUSLINES could
1486 	 * be added there, and code added here for determining the displayed
1487 	 * string length, as we do above for CJK.  - FM
1488 	 */
1489 	for (i = 0, len = 0; text_buff[i] != '\0' && len < max_length; i++) {
1490 	    if (text_buff[i] != CH_ESC) {	/* S/390 -- gil -- 2119 */
1491 		buffer[len++] = text_buff[i];
1492 	    }
1493 	}
1494 	buffer[len] = '\0';
1495 #endif
1496     }
1497 
1498     /*
1499      * Move to the desired statusline window and output the text highlighted.
1500      * - FM
1501      */
1502     if (LYStatusLine >= 0) {
1503 	if (LYStatusLine < LYlines - 1) {
1504 	    at_lineno = LYStatusLine;
1505 	} else {
1506 	    at_lineno = LYlines - 1;
1507 	}
1508     } else if (user_mode == NOVICE_MODE) {
1509 	at_lineno = LYlines - 3;
1510     } else {
1511 	at_lineno = LYlines - 1;
1512     }
1513     LYmove(at_lineno, 0);
1514     LYclrtoeol();
1515 
1516     if (buffer[0] != '\0') {
1517 	BOOLEAN has_CJK = FALSE;
1518 
1519 	if (IS_CJK_TTY) {
1520 	    for (i = 0; buffer[i] != '\0'; i++) {
1521 		if (buffer[i] & 0x80) {
1522 		    has_CJK = TRUE;
1523 		    break;
1524 		}
1525 	    }
1526 	}
1527 
1528 	if (has_CJK
1529 #ifdef HAVE_UTF8_STATUSLINES
1530 	    || IS_UTF8_TTY
1531 #endif
1532 	    ) {
1533 	    LYrefresh();
1534 	}
1535 #ifndef USE_COLOR_STYLE
1536 	lynx_start_status_color();
1537 	LYaddstr(buffer);
1538 	lynx_stop_status_color();
1539 #else
1540 	/* draw the status bar in the STATUS style */
1541 	{
1542 	    int y, x;
1543 	    int a = ((StrNCmp(buffer, ALERT_FORMAT, ALERT_PREFIX_LEN)
1544 		      || !hashStyles[s_alert].name)
1545 		     ? s_status
1546 		     : s_alert);
1547 
1548 	    LynxChangeStyle(a, STACK_ON);
1549 	    LYaddstr(buffer);
1550 	    wbkgdset(LYwin,
1551 		     ((lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
1552 		      ? (chtype) hashStyles[a].color
1553 		      : A_NORMAL) | ' ');
1554 	    LYGetYX(y, x);
1555 	    (void) x;
1556 	    if (y == at_lineno) {
1557 		LYclrtoeol();
1558 	    }
1559 	    if (!(lynx_has_color && LYShowColor >= SHOW_COLOR_ON))
1560 		wbkgdset(LYwin, A_NORMAL | ' ');
1561 	    else if (s_normal != NOSTYLE)
1562 		wbkgdset(LYwin, (chtype) (hashStyles[s_normal].color | ' '));
1563 	    else
1564 		wbkgdset(LYwin, (chtype) (displayStyles[DSTYLE_NORMAL].color | ' '));
1565 	    LynxChangeStyle(a, STACK_OFF);
1566 	}
1567 #endif
1568     }
1569     LYrefresh();
1570 
1571     return;
1572 }
1573 
novice_lines(int lineno)1574 static const char *novice_lines(int lineno)
1575 {
1576     switch (lineno) {
1577     case 0:
1578 	return NOVICE_LINE_TWO_A;
1579     case 1:
1580 	return NOVICE_LINE_TWO_B;
1581     case 2:
1582 	return NOVICE_LINE_TWO_C;
1583     default:
1584 	return "";
1585     }
1586 }
1587 
1588 static int lineno = 0;
1589 
toggle_novice_line(void)1590 void toggle_novice_line(void)
1591 {
1592     lineno++;
1593     if (*novice_lines(lineno) == '\0')
1594 	lineno = 0;
1595     return;
1596 }
1597 
noviceline(int more_flag GCC_UNUSED)1598 void noviceline(int more_flag GCC_UNUSED)
1599 {
1600     if (dump_output_immediately)
1601 	return;
1602 
1603     LYmove(LYlines - 2, 0);
1604     LYclrtoeol();
1605     LYaddstr(NOVICE_LINE_ONE);
1606 
1607     LYmove(LYlines - 1, 0);
1608     LYclrtoeol();
1609 #if defined(DIRED_SUPPORT ) && defined(OK_OVERRIDE)
1610     if (lynx_edit_mode && !no_dired_support)
1611 	LYaddstr(DIRED_NOVICELINE);
1612     else
1613 #endif /* DIRED_SUPPORT && OK_OVERRIDE */
1614 
1615     if (LYUseNoviceLineTwo)
1616 	LYaddstr(NOVICE_LINE_TWO);
1617     else
1618 	LYaddstr(novice_lines(lineno));
1619 
1620     LYrefresh();
1621     return;
1622 }
1623 
1624 #if defined(MISC_EXP) || defined(TTY_DEVICE) || defined(HAVE_TTYNAME)
1625 /*
1626  * If the standard input is not a tty, and Lynx is really reading from the
1627  * standard input, attempt to reopen it, pointing to a real tty.  Normally
1628  * this would happen if the user pipes data to Lynx and wants to run
1629  * interactively after that.
1630  *
1631  * Returns:
1632  *     1  if successfully reopened
1633  *    -1  if we cannot reopen
1634  *     0  if we do not have to reopen
1635  */
LYReopenInput(void)1636 int LYReopenInput(void)
1637 {
1638     int result = 0;
1639     int fd;
1640 
1641     if ((fd = fileno(stdin)) == 0
1642 	&& !isatty(fd)
1643 	&& LYConsoleInputFD(FALSE) == fd) {
1644 	const char *term_name = NULL;
1645 	int new_fd = -1;
1646 
1647 #ifdef HAVE_TTYNAME
1648 	if (isatty(fileno(stdout)) &&
1649 	    (term_name = ttyname(fileno(stdout))) != NULL)
1650 	    new_fd = open(term_name, O_RDONLY);
1651 
1652 	if (new_fd == -1 &&
1653 	    isatty(fileno(stderr)) &&
1654 	    (term_name = ttyname(fileno(stderr))) != NULL)
1655 	    new_fd = open(term_name, O_RDONLY);
1656 #endif
1657 
1658 #ifdef HAVE_CTERMID
1659 	if (new_fd == -1 &&
1660 	    (term_name = ctermid(NULL)) != NULL)
1661 	    new_fd = open(term_name, O_RDONLY);
1662 #endif
1663 
1664 #ifdef TTY_DEVICE
1665 	if (new_fd == -1)
1666 	    new_fd = open(term_name = TTY_DEVICE, O_RDONLY);
1667 #endif
1668 
1669 	CTRACE((tfp, "LYReopenInput open(%s) returned %d.\n", term_name, new_fd));
1670 	if (new_fd >= 0) {
1671 	    FILE *frp;
1672 
1673 	    close(new_fd);
1674 	    frp = freopen(term_name, "r", stdin);
1675 	    CTRACE((tfp,
1676 		    "LYReopenInput freopen(%s,\"r\",stdin) returned %p, stdin is now %p with fd %d.\n",
1677 		    term_name, (void *) frp, (void *) stdin, fileno(stdin)));
1678 	    result = 1;
1679 	} else {
1680 	    result = -1;
1681 	}
1682     }
1683     return result;
1684 }
1685 #endif
1686 
1687 #if defined(NSL_FORK) || defined(MISC_EXP) || defined (TTY_DEVICE) || defined(HAVE_TTYNAME)
1688 /*
1689  * Returns the file descriptor from which keyboard input is expected, or INVSOC
1690  * (-1) if not available.  If need_selectable is true, returns non-INVSOC fd
1691  * only if select() is possible - actually, currently only checks if fd is
1692  * connected to a tty.  - kw
1693  */
LYConsoleInputFD(int need_selectable)1694 int LYConsoleInputFD(int need_selectable)
1695 {
1696     int fd = INVSOC;
1697 
1698 #ifdef USE_SLANG
1699     if (!LYCursesON)
1700 	fd = fileno(stdin);
1701 #if ((SLANG_VERSION >= 9919) && defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__))
1702     /* SLang_TT_Read_FD introduced in slang 0.99.19, from its changelog:
1703      * SLang_TT_Read_FD variable is now available for unix.  This is the file
1704      * descriptor used by SLang_getkey.  */
1705     else
1706 	fd = SLang_TT_Read_FD;
1707 #endif /* SLANG_VERSION >= 9919 */
1708 #else /* !USE_SLANG */
1709     fd = fileno(stdin);
1710 #endif /* !USE_SLANG */
1711 
1712     if (need_selectable && fd != INVSOC) {
1713 	if (isatty(fd)) {
1714 	    return fd;
1715 	} else {
1716 	    return INVSOC;
1717 	}
1718     }
1719     return fd;
1720 }
1721 #endif /* NSL_FORK || MISC_EXP */
1722 
1723 static int fake_zap = 0;
1724 
LYFakeZap(int set)1725 void LYFakeZap(int set)
1726 {
1727     if (set && fake_zap < 1) {
1728 	CTRACE((tfp, "\r *** Set simulated 'Z'"));
1729 	if (fake_zap)
1730 	    CTRACE((tfp, ", %d pending", fake_zap));
1731 	CTRACE((tfp, " ***\n"));
1732 	fake_zap++;
1733     } else if (!set && fake_zap) {
1734 	CTRACE((tfp, "\r *** Unset simulated 'Z'"));
1735 	CTRACE((tfp, ", %d pending", fake_zap));
1736 	CTRACE((tfp, " ***\n"));
1737 	fake_zap = 0;
1738     }
1739 
1740 }
1741 
DontCheck(void)1742 static int DontCheck(void)
1743 {
1744     static time_t last;
1745     time_t next;
1746 
1747     /** Curses or slang setup was not invoked **/
1748     if (dump_output_immediately)
1749 	return (TRUE);
1750 
1751     if (LYHaveCmdScript())	/* we may be running from a script */
1752 	return (TRUE);
1753 
1754 #ifdef MISC_EXP
1755     if (LYNoZapKey)
1756 	return (TRUE);
1757 #endif
1758     /*
1759      * Avoid checking interrupts more than one per second, since it is a slow
1760      * and expensive operation - TD
1761      */
1762 #ifdef HAVE_GETTIMEOFDAY
1763 #undef timezone			/* U/Win defines a conflicting macro */
1764     {
1765 	struct timeval tv;
1766 
1767 	gettimeofday(&tv, (struct timezone *) 0);
1768 	next = tv.tv_usec / 100000L;	/* 0.1 seconds is a compromise */
1769     }
1770 #else
1771     next = time((time_t *) 0);
1772 #endif
1773     if (next == last)
1774 	return (TRUE);
1775 
1776     last = next;
1777     return FALSE;
1778 }
1779 
HTCheckForInterrupt(void)1780 int HTCheckForInterrupt(void)
1781 {
1782     int c;
1783     int cmd;
1784 
1785     if (fake_zap > 0) {
1786 	fake_zap--;
1787 	CTRACE((tfp, "\r *** Got simulated 'Z' ***\n"));
1788 	CTRACE_FLUSH(tfp);
1789 	CTRACE_SLEEP(AlertSecs);
1790 	return ((int) TRUE);
1791     }
1792 
1793     /** Curses or slang setup was not invoked **/
1794     if (DontCheck())
1795 	return ((int) FALSE);
1796 
1797 #ifndef VMS			/* UNIX stuff: */
1798 
1799 #if !defined(_WINDOWS) || defined(__MINGW32__)
1800 
1801     /*
1802      * First, check if there is a character.
1803      */
1804 #ifdef USE_SLANG
1805     /** No keystroke was entered
1806 	Note that this isn't taking possible SOCKSification
1807 	and the socks_flag into account, and may fail on the
1808 	slang library's select() when SOCKSified. - FM **/
1809 #ifdef DJGPP_KEYHANDLER
1810     if (0 == _bios_keybrd(_NKEYBRD_READY))
1811 	return (FALSE);
1812 #else
1813     if (0 == SLang_input_pending(0))
1814 	return (FALSE);
1815 #endif /* DJGPP_KEYHANDLER */
1816 
1817 #else /* Unix curses: */
1818     {
1819 	struct timeval socket_timeout;
1820 	int ret = 0;
1821 	fd_set readfds;
1822 
1823 	socket_timeout.tv_sec = 0;
1824 	socket_timeout.tv_usec = 0;
1825 	FD_ZERO(&readfds);
1826 	FD_SET(0, &readfds);
1827 #ifdef SOCKS
1828 	if (socks_flag)
1829 	    ret = Rselect(1, &readfds, NULL, NULL, &socket_timeout);
1830 	else
1831 #endif /* SOCKS */
1832 	    ret = select(1, &readfds, NULL, NULL, &socket_timeout);
1833 
1834 	/** Suspended? **/
1835 	if ((ret == -1) && (SOCKET_ERRNO == EINTR))
1836 	    return ((int) FALSE);
1837 
1838 	/** No keystroke was entered? **/
1839 	if (!FD_ISSET(0, &readfds))
1840 	    return ((int) FALSE);
1841     }
1842 #endif /* USE_SLANG */
1843 
1844 #endif /* !_WINDOWS */
1845 
1846     /*
1847      * Now, read the character.
1848      */
1849 #if defined(USE_CURSES_NODELAY)
1850     nodelay(LYwin, TRUE);
1851     c = LYgetch();
1852     nodelay(LYwin, FALSE);
1853 #elif defined(USE_SLANG) && defined(_WINDOWS)
1854     if (!SLang_input_pending(0))
1855 	return ((int) FALSE);
1856     c = LYgetch();
1857 #else
1858     c = LYgetch();
1859 #endif
1860 
1861 #else /* VMS: */
1862     extern int typeahead(void);
1863 
1864     /** Control-C or Control-Y and a 'N'o reply to exit query **/
1865     if (HadVMSInterrupt) {
1866 	HadVMSInterrupt = FALSE;
1867 	return ((int) TRUE);
1868     }
1869 
1870     c = typeahead();
1871 
1872 #endif /* !VMS */
1873 
1874     /*
1875      * 'c' contains whatever character we're able to read from keyboard
1876      */
1877 
1878     /** Keyboard 'Z' or 'z', or Control-G or Control-C **/
1879     if (LYCharIsINTERRUPT(c))
1880 	return ((int) TRUE);
1881 
1882     /* There is a subset of mainloop() actions available at this stage:  no new
1883      * getfile() cycle is possible until the previous finished.  Currently we
1884      * have scrolling in partial mode, toggling of trace log, and pasting.
1885      * User search now in progress...
1886      */
1887     cmd = (LKC_TO_LAC(keymap, c));
1888     switch (cmd) {
1889     case LYK_TRACE_TOGGLE:	/*  Toggle TRACE mode. */
1890 	handle_LYK_TRACE_TOGGLE();
1891 	break;
1892 #ifdef CAN_CUT_AND_PASTE
1893     case LYK_TO_CLIPBOARD:{	/* ^S */
1894 	    const char *s = LYDownLoadAddress();
1895 
1896 	    if (!s || !*s || put_clip(s))
1897 		HTInfoMsg(gettext("Copy to clipboard failed."));
1898 	    else
1899 		HTInfoMsg(gettext("Download document URL put to clipboard."));
1900 	    break;
1901 	}
1902 #endif /* defined CAN_CUT_AND_PASTE */
1903     default:
1904 #ifdef DISP_PARTIAL
1905 	/* OK, we got several lines from new document and want to scroll... */
1906 	if (display_partial && (NumOfLines_partial > 2)) {
1907 	    BOOLEAN do_refresh;
1908 	    int res;
1909 	    int Newline_partial = LYGetNewline();
1910 
1911 	    switch (cmd) {
1912 	    case LYK_WHEREIS:	/* search within the document */
1913 	    case LYK_NEXT:	/* search for the next occurrence in the document */
1914 	    case LYK_PREV:	/* search for the previous occurrence in the document */
1915 		handle_LYK_WHEREIS(cmd, &do_refresh);
1916 		if (www_search_result != -1) {
1917 		    Newline_partial = www_search_result;
1918 		    www_search_result = -1;	/* reset */
1919 		}
1920 		break;
1921 
1922 	    case LYK_FASTBACKW_LINK:
1923 		if (Newline_partial <= (display_lines) + 1) {
1924 		    Newline_partial -= display_lines;
1925 		} else if ((res =
1926 			    HTGetLinkOrFieldStart(-1,
1927 						  &Newline_partial, NULL,
1928 						  -1, TRUE)) == LINK_LINE_FOUND) {
1929 		    Newline_partial++;
1930 		} else if (res == LINK_DO_ARROWUP) {
1931 		    Newline_partial -= display_lines;
1932 		}
1933 		break;
1934 	    case LYK_FASTFORW_LINK:
1935 		if (HText_canScrollDown()) {
1936 		    /* This is not an exact science... - kw */
1937 		    if (HTGetLinkOrFieldStart(HText_LinksInLines(HTMainText,
1938 								 Newline_partial,
1939 								 display_lines)
1940 					      - 1,
1941 					      &Newline_partial, NULL,
1942 					      1, TRUE) == LINK_LINE_FOUND) {
1943 			Newline_partial++;
1944 		    }
1945 		}
1946 		break;
1947 	    case LYK_PREV_PAGE:
1948 		if (Newline_partial > 1)
1949 		    Newline_partial -= display_lines;
1950 		break;
1951 	    case LYK_NEXT_PAGE:
1952 		if (HText_canScrollDown())
1953 		    Newline_partial += display_lines;
1954 		break;
1955 	    case LYK_UP_HALF:
1956 		if (Newline_partial > 1)
1957 		    Newline_partial -= (display_lines / 2);
1958 		break;
1959 	    case LYK_DOWN_HALF:
1960 		if (HText_canScrollDown())
1961 		    Newline_partial += (display_lines / 2);
1962 		break;
1963 	    case LYK_UP_TWO:
1964 		if (Newline_partial > 1)
1965 		    Newline_partial -= 2;
1966 		break;
1967 	    case LYK_DOWN_TWO:
1968 		if (HText_canScrollDown())
1969 		    Newline_partial += 2;
1970 		break;
1971 	    case LYK_HOME:
1972 		if (Newline_partial > 1)
1973 		    Newline_partial = 1;
1974 		break;
1975 	    case LYK_END:
1976 		if (HText_canScrollDown())
1977 		    Newline_partial = HText_getNumOfLines() - display_lines + 1;
1978 		/* calculate for "current" bottom value */
1979 		break;
1980 	    case LYK_REFRESH:
1981 		break;
1982 	    default:
1983 		/** Other or no keystrokes **/
1984 		return ((int) FALSE);
1985 	    }			/* end switch */
1986 	    if (Newline_partial < 1)
1987 		Newline_partial = 1;
1988 	    if (LYMainLoop_pageDisplay(Newline_partial))
1989 		NumOfLines_partial = HText_getNumOfLines();
1990 	}
1991 #endif /* DISP_PARTIAL */
1992 	break;
1993     }				/* end switch */
1994     /** Other or no keystrokes **/
1995     return ((int) FALSE);
1996 }
1997 
1998 /*
1999  * Check if the given filename looks like it's an absolute pathname, i.e.,
2000  * references a directory.
2001  */
LYisAbsPath(const char * path)2002 BOOLEAN LYisAbsPath(const char *path)
2003 {
2004     BOOLEAN result = FALSE;
2005 
2006     if (non_empty(path)) {
2007 #ifdef VMS
2008 	result = TRUE;
2009 #else
2010 #if defined(USE_DOS_DRIVES)
2011 	result = (BOOLEAN) (LYIsPathSep(path[0])
2012 			    || (LYIsDosDrive(path)
2013 				&& LYIsPathSep(path[2])));
2014 #else
2015 	result = (BOOLEAN) (LYIsPathSep(path[0]));
2016 #endif /* USE_DOS_DRIVES */
2017 #endif
2018     }
2019     return result;
2020 }
2021 
2022 /*
2023  * Check if the given filename is the root path, e.g., "/" on Unix.
2024  */
LYisRootPath(const char * path)2025 BOOLEAN LYisRootPath(const char *path)
2026 {
2027 #if defined(USE_DOS_DRIVES)
2028     if (strlen(path) == 3
2029 	&& LYIsDosDrive(path)
2030 	&& LYIsPathSep(path[2]))
2031 	return TRUE;
2032 #endif
2033     return (BOOL) ((strlen(path) == 1) && LYIsPathSep(path[0]));
2034 }
2035 
2036 /*
2037  * A file URL for a remote host is an obsolete ftp URL.
2038  * Return YES only if we're certain it's a local file.  - FM
2039  */
LYisLocalFile(const char * filename)2040 BOOLEAN LYisLocalFile(const char *filename)
2041 {
2042     char *host = NULL;
2043     char *acc_method = NULL;
2044     char *cp;
2045 
2046     if (!filename)
2047 	return NO;
2048     if (!(host = HTParse(filename, "", PARSE_HOST)))
2049 	return NO;
2050     if (!*host) {
2051 	FREE(host);
2052 	return NO;
2053     }
2054 
2055     if ((cp = strchr(host, ':')) != NULL)
2056 	*cp = '\0';
2057 
2058     if ((acc_method = HTParse(filename, "", PARSE_ACCESS))) {
2059 	if (0 == strcmp("file", acc_method) &&
2060 	    (0 == strcmp(host, "localhost") ||
2061 	     LYSameFilename(host, HTHostName()))) {
2062 	    FREE(host);
2063 	    FREE(acc_method);
2064 	    return YES;
2065 	}
2066     }
2067 
2068     FREE(host);
2069     FREE(acc_method);
2070     return NO;
2071 }
2072 
2073 /*
2074  * Utility for checking URLs with a host field.  Return YES only if we're
2075  * certain it's the local host.  - FM
2076  */
LYisLocalHost(const char * filename)2077 BOOLEAN LYisLocalHost(const char *filename)
2078 {
2079     char *host = NULL;
2080     char *cp;
2081 
2082     if (!filename)
2083 	return NO;
2084     if (!(host = HTParse(filename, "", PARSE_HOST)))
2085 	return NO;
2086     if (!*host) {
2087 	FREE(host);
2088 	return NO;
2089     }
2090 
2091     if ((cp = strchr(host, ':')) != NULL)
2092 	*cp = '\0';
2093 
2094     if ((LYSameFilename(host, "localhost") ||
2095 	 LYSameFilename(host, LYHostName) ||
2096 	 LYSameFilename(host, HTHostName()))) {
2097 	FREE(host);
2098 	return YES;
2099     }
2100 
2101     FREE(host);
2102     return NO;
2103 }
2104 
2105 /*
2106  * Free an HTList that contains strings.
2107  */
LYFreeStringList(HTList * list)2108 void LYFreeStringList(HTList *list)
2109 {
2110     if (list != NULL) {
2111 	char *argument;
2112 	HTList *cur = list;
2113 
2114 	while (NULL != (argument = (char *) HTList_nextObject(cur))) {
2115 	    FREE(argument);
2116 	}
2117 	HTList_delete(list);
2118     }
2119 }
2120 
2121 /*
2122  * Utility for freeing the list of local host aliases.  - FM
2123  */
LYLocalhostAliases_free(void)2124 void LYLocalhostAliases_free(void)
2125 {
2126     LYFreeStringList(localhost_aliases);
2127     localhost_aliases = NULL;
2128 }
2129 
2130 /*
2131  * Utility for listing hosts to be treated as local aliases.  - FM
2132  */
LYAddLocalhostAlias(char * alias)2133 void LYAddLocalhostAlias(char *alias)
2134 {
2135     char *LocalAlias = NULL;
2136 
2137     if (!non_empty(alias))
2138 	return;
2139 
2140     if (!localhost_aliases) {
2141 	localhost_aliases = HTList_new();
2142 #ifdef LY_FIND_LEAKS
2143 	atexit(LYLocalhostAliases_free);
2144 #endif
2145     }
2146 
2147     StrAllocCopy(LocalAlias, alias);
2148     HTList_addObject(localhost_aliases, LocalAlias);
2149 
2150     return;
2151 }
2152 
2153 /*
2154  * Utility for checking URLs with a host field.  Return YES only if we've
2155  * listed the host as a local alias.  - FM
2156  */
LYisLocalAlias(const char * filename)2157 BOOLEAN LYisLocalAlias(const char *filename)
2158 {
2159     char *host = NULL;
2160     char *alias;
2161     char *cp;
2162     HTList *cur = localhost_aliases;
2163 
2164     if (!cur || !filename)
2165 	return NO;
2166     if (!(host = HTParse(filename, "", PARSE_HOST)))
2167 	return NO;
2168     if (!(*host)) {
2169 	FREE(host);
2170 	return NO;
2171     }
2172 
2173     if ((cp = strchr(host, ':')) != NULL)
2174 	*cp = '\0';
2175 
2176     while (NULL != (alias = (char *) HTList_nextObject(cur))) {
2177 	if (LYSameFilename(host, alias)) {
2178 	    FREE(host);
2179 	    return YES;
2180 	}
2181     }
2182 
2183     FREE(host);
2184     return NO;
2185 }
2186 
2187 /*
2188  *  This function checks for a URL with an unknown scheme,
2189  *  but for which proxying has been set up, and if so,
2190  *  returns PROXY_URL_TYPE. - FM
2191  *
2192  *  If a colon is present but the string segment which
2193  *  precedes it is not being proxied, and we can be sure
2194  *  that what follows the colon is not a port field,
2195  *  it returns UNKNOWN_URL_TYPE.  Otherwise, it returns
2196  *  0 (not a URL). - FM
2197  */
LYCheckForProxyURL(char * filename)2198 UrlTypes LYCheckForProxyURL(char *filename)
2199 {
2200     char *cp = filename;
2201     char *cp1;
2202     char *cp2 = NULL;
2203 
2204     /*
2205      * Don't crash on an empty argument.
2206      */
2207     if (isEmpty(cp))
2208 	return (NOT_A_URL_TYPE);
2209 
2210     /* kill beginning spaces */
2211     cp = LYSkipBlanks(cp);
2212 
2213     /*
2214      * Check for a colon, and if present,
2215      * see if we have proxying set up.
2216      */
2217     if ((cp1 = strchr((cp + 1), ':')) != NULL) {
2218 	if ((cp2 = strchr((cp + 1), '/')) != NULL && cp2 < cp1)
2219 	    return (NOT_A_URL_TYPE);
2220 	*cp1 = '\0';
2221 	cp2 = NULL;
2222 	StrAllocCopy(cp2, cp);
2223 	*cp1 = ':';
2224 	StrAllocCat(cp2, "_proxy");
2225 	if (LYGetEnv(cp2) != NULL) {
2226 	    FREE(cp2);
2227 	    return (PROXY_URL_TYPE);
2228 	}
2229 	FREE(cp2);
2230 #if defined (USE_DOS_DRIVES)
2231 	if (LYIsDosDrive(cp))
2232 	    return (NOT_A_URL_TYPE);
2233 #endif
2234 	cp1++;
2235 	if (!*cp) {
2236 	    return (NOT_A_URL_TYPE);
2237 	} else if (isdigit(UCH(*cp1))) {
2238 	    while (*cp1 && isdigit(UCH(*cp1)))
2239 		cp1++;
2240 	    if (*cp1 && !LYIsHtmlSep(*cp1))
2241 		return (UNKNOWN_URL_TYPE);
2242 	} else {
2243 	    return (UNKNOWN_URL_TYPE);
2244 	}
2245     }
2246 
2247     return (NOT_A_URL_TYPE);
2248 }
2249 
2250 /*
2251  * Compare a "type:" string, replacing it by the comparison-string if it
2252  * matches (and return true in that case).
2253  */
compare_type(char * tst,const char * cmp,size_t len)2254 static BOOLEAN compare_type(char *tst,
2255 			    const char *cmp,
2256 			    size_t len)
2257 {
2258     if (!strncasecomp(tst, cmp, (int) len)) {
2259 	if (StrNCmp(tst, cmp, len)) {
2260 	    size_t i;
2261 
2262 	    for (i = 0; i < len; i++)
2263 		tst[i] = cmp[i];
2264 	}
2265 	return TRUE;
2266     }
2267     return FALSE;
2268 }
2269 #define CompareType(tst,cmp,len) compare_type((tst),(cmp),(size_t)(len))
2270 
2271 #define DoubleHtmlSep(s) (LYIsHtmlSep((s)[0]) && LYIsHtmlSep((s)[1]))
2272 #define compare_two(tst,cmp,len,limit) \
2273 	((len + 2) <= limit \
2274 	&& DoubleHtmlSep(tst + len) \
2275 	&& CompareType(tst, cmp, len))
2276 
2277 /*
2278  *  Must recognize a URL and return the type.
2279  *  If recognized, based on a case-insensitive
2280  *  analysis of the scheme field, ensures that
2281  *  the scheme field has the expected case.
2282  *
2283  *  Returns 0 (not a URL) for a NULL argument,
2284  *  one which lacks a colon.
2285  *
2286  *  Chains to LYCheckForProxyURL() if a colon
2287  *  is present but the type is not recognized.
2288  */
is_url(char * filename)2289 UrlTypes is_url(char *filename)
2290 {
2291     char *cp = filename;
2292     char *cp1;
2293     UrlTypes result = NOT_A_URL_TYPE;
2294     int limit;
2295 
2296     /*
2297      * Don't crash on an empty argument.
2298      */
2299     if (isEmpty(cp))
2300 	return (result);
2301 
2302     /*
2303      * Can't be a URL if it lacks a colon and if it starts with '[' it's
2304      * probably IPv6 adress.
2305      */
2306     if (NULL == strchr(cp, ':') || cp[0] == '[')
2307 	return (result);
2308 
2309     /*
2310      * Kill beginning spaces.
2311      */
2312     cp = LYSkipBlanks(cp);
2313 
2314     /*
2315      * Can't be a URL if it starts with a slash.  So return immediately for
2316      * this common case, also to avoid false positives if there was a colon
2317      * later in the string.  Also can't be a URL if it starts with a colon.  -
2318      * KW
2319      */
2320     if (*cp == ':' || LYIsHtmlSep(*cp)) {
2321 	result = NOT_A_URL_TYPE;
2322 
2323     } else {
2324 	limit = (int) strlen(cp);
2325 	switch (*cp) {
2326 	case 'L':
2327 	case 'l':
2328 	    /*
2329 	     * Lynx internal pages ("LYNXfoo:" or "lynxfoo:") start with 'l' or
2330 	     * 'L', other URLs aren't.
2331 	     */
2332 	    if (CompareType(cp, STR_LYNXEXEC, LEN_LYNXEXEC)) {
2333 		/*
2334 		 * Special External Lynx type to handle execution of commands
2335 		 * or scripts which require a pause to read the screen upon
2336 		 * completion.
2337 		 */
2338 		result = LYNXEXEC_URL_TYPE;
2339 
2340 	    } else if (CompareType(cp, STR_LYNXPROG, LEN_LYNXPROG)) {
2341 		/*
2342 		 * Special External Lynx type to handle execution of commands,
2343 		 * scripts or programs with do not require a pause to read
2344 		 * screen upon completion.
2345 		 */
2346 		result = LYNXPROG_URL_TYPE;
2347 
2348 	    } else if (CompareType(cp, STR_LYNXCGI, LEN_LYNXCGI)) {
2349 		/*
2350 		 * Special External Lynx type to handle cgi scripts.
2351 		 */
2352 		result = LYNXCGI_URL_TYPE;
2353 
2354 	    } else if (CompareType(cp, STR_LYNXPRINT, LEN_LYNXPRINT)) {
2355 		/*
2356 		 * Special Internal Lynx type.
2357 		 */
2358 		result = LYNXPRINT_URL_TYPE;
2359 
2360 	    } else if (CompareType(cp, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)) {
2361 		/*
2362 		 * Special Internal Lynx type.
2363 		 */
2364 		result = LYNXOPTIONS_URL_TYPE;
2365 
2366 	    } else if (CompareType(cp, STR_LYNXCFG, LEN_LYNXCFG)) {
2367 		/*
2368 		 * Special Internal Lynx type.
2369 		 */
2370 		result = LYNXCFG_URL_TYPE;
2371 
2372 	    } else if (CompareType(cp, STR_LYNXMESSAGES, LEN_LYNXMESSAGES)) {
2373 		/*
2374 		 * Special Internal Lynx type.
2375 		 */
2376 		result = LYNXMESSAGES_URL_TYPE;
2377 
2378 	    } else if (CompareType(cp, STR_LYNXCFLAGS, LEN_LYNXCFLAGS)) {
2379 		/*
2380 		 * Special Internal Lynx type.
2381 		 */
2382 		result = LYNXCOMPILE_OPTS_URL_TYPE;
2383 
2384 	    } else if (CompareType(cp, STR_LYNXDOWNLOAD, LEN_LYNXDOWNLOAD)) {
2385 		/*
2386 		 * Special Internal Lynx type.
2387 		 */
2388 		result = LYNXDOWNLOAD_URL_TYPE;
2389 
2390 	    } else if (CompareType(cp, STR_LYNXDIRED, LEN_LYNXDIRED)) {
2391 		/*
2392 		 * Special Internal Lynx type.
2393 		 */
2394 		result = LYNXDIRED_URL_TYPE;
2395 
2396 	    } else if (CompareType(cp, STR_LYNXHIST, LEN_LYNXHIST)) {
2397 		/*
2398 		 * Special Internal Lynx type.
2399 		 */
2400 		result = LYNXHIST_URL_TYPE;
2401 
2402 #ifdef USE_CACHEJAR
2403 	    } else if (CompareType(cp, STR_LYNXCACHE, LEN_LYNXCACHE)) {
2404 		/*
2405 		 * Special Internal Lynx type.
2406 		 */
2407 		result = LYNXCACHE_URL_TYPE;
2408 #endif
2409 
2410 	    } else if (CompareType(cp, STR_LYNXKEYMAP, LEN_LYNXKEYMAP)) {
2411 		/*
2412 		 * Special Internal Lynx type.
2413 		 */
2414 		result = LYNXKEYMAP_URL_TYPE;
2415 
2416 	    } else if (CompareType(cp, STR_LYNXIMGMAP, LEN_LYNXIMGMAP)) {
2417 		/*
2418 		 * Special Internal Lynx type.
2419 		 */
2420 		/* force lower/uppercase of next part */
2421 		(void) is_url(&cp[LEN_LYNXIMGMAP]);
2422 		result = LYNXIMGMAP_URL_TYPE;
2423 
2424 	    } else if (CompareType(cp, STR_LYNXCOOKIE, LEN_LYNXCOOKIE)) {
2425 		/*
2426 		 * Special Internal Lynx type.
2427 		 */
2428 		result = LYNXCOOKIE_URL_TYPE;
2429 	    }
2430 	    break;
2431 #ifndef DISABLE_NEWS
2432 	    /*
2433 	     * NEWSfoo:  schemes -
2434 	     */
2435 	case 'N':
2436 	case 'n':
2437 	    if (CompareType(cp, STR_NEWS_URL, LEN_NEWS_URL)) {
2438 		result = NEWS_URL_TYPE;
2439 
2440 	    } else if (CompareType(cp, STR_NNTP_URL, LEN_NNTP_URL)) {
2441 		result = NNTP_URL_TYPE;
2442 
2443 	    } else if (CompareType(cp, "newspost:", 9)) {
2444 		/*
2445 		 * Special Lynx type to handle news posts.
2446 		 */
2447 		result = NEWSPOST_URL_TYPE;
2448 
2449 	    } else if (CompareType(cp, "newsreply:", 10)) {
2450 		/*
2451 		 * Special Lynx type to handle news replies (followups).
2452 		 */
2453 		result = NEWSREPLY_URL_TYPE;
2454 	    }
2455 	    break;
2456 
2457 	    /*
2458 	     * SNEWSfoo:  schemes -
2459 	     */
2460 	case 'S':
2461 	case 's':
2462 	    if (CompareType(cp, STR_SNEWS_URL, LEN_SNEWS_URL)) {
2463 		result = SNEWS_URL_TYPE;
2464 
2465 	    } else if (CompareType(cp, "snewspost:", 10)) {
2466 		/*
2467 		 * Special Lynx type to handle snews posts.
2468 		 */
2469 		result = NEWSPOST_URL_TYPE;
2470 
2471 	    } else if (CompareType(cp, "snewsreply:", 11)) {
2472 		/*
2473 		 * Special Lynx type to handle snews replies (followups).
2474 		 */
2475 		result = NEWSREPLY_URL_TYPE;
2476 	    }
2477 	    break;
2478 #endif
2479 	case 'M':
2480 	case 'm':
2481 	    if (CompareType(cp, STR_MAILTO_URL, LEN_MAILTO_URL)) {
2482 		result = MAILTO_URL_TYPE;
2483 	    }
2484 	    break;
2485 
2486 	case 'F':
2487 	case 'f':
2488 	    if (CompareType(cp, STR_FILE_URL, LEN_FILE_URL)) {
2489 		if (LYisLocalFile(cp)) {
2490 		    result = FILE_URL_TYPE;
2491 		} else if (DoubleHtmlSep(cp + LEN_FILE_URL)) {
2492 		    result = FTP_URL_TYPE;
2493 		}
2494 	    }
2495 #ifndef DISABLE_FTP
2496 	    else if (compare_two(cp, STR_FTP_URL, LEN_FTP_URL, limit)) {
2497 		result = FTP_URL_TYPE;
2498 	    }
2499 #endif
2500 #ifndef DISABLE_FINGER
2501 	    else if (compare_two(cp, STR_FINGER_URL, LEN_FINGER_URL, limit)) {
2502 		result = FINGER_URL_TYPE;
2503 	    }
2504 #endif
2505 	    break;
2506 
2507 	case 'B':
2508 	case 'b':
2509 #ifndef DISABLE_BIBP
2510 	    if (CompareType(cp, STR_BIBP_URL, LEN_BIBP_URL)) {
2511 		result = BIBP_URL_TYPE;
2512 	    }
2513 #endif
2514 	    break;
2515 
2516 	case 'D':
2517 	case 'd':
2518 	    if (CompareType(cp, "data:", 5)) {
2519 		result = DATA_URL_TYPE;
2520 	    }
2521 	    break;
2522 
2523 	default:
2524 	    if (limit >= 3
2525 		&& ((cp1 = strchr(cp + 3, ':')) == NULL
2526 		    || !DoubleHtmlSep(cp1 + 1))) {
2527 		/*
2528 		 * If it doesn't contain "://", and it's not one of the the
2529 		 * above, it can't be a URL with a scheme we know, so check if
2530 		 * it's an unknown scheme for which proxying has been set up.
2531 		 * - FM
2532 		 */
2533 		if (cp1 != NULL
2534 		    && (cp1 - cp) > 1	/* exclude DOS-style device:/path */
2535 		    && LYisAbsPath(cp1 + 1)) {
2536 		    result = NCFTP_URL_TYPE;
2537 		}
2538 
2539 	    } else {
2540 		switch (*cp) {
2541 		case 'H':
2542 		case 'h':
2543 		    if (CompareType(cp, STR_HTTP_URL, LEN_HTTP_URL)) {
2544 			result = HTTP_URL_TYPE;
2545 
2546 		    } else if (CompareType(cp, STR_HTTPS_URL, LEN_HTTPS_URL)) {
2547 			result = HTTPS_URL_TYPE;
2548 		    }
2549 		    break;
2550 
2551 #ifndef DISABLE_GOPHER
2552 		case 'G':
2553 		case 'g':
2554 		    if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) {
2555 			if (strlen(cp) >= 11
2556 			    && (cp1 = strchr(cp + 11, '/')) != NULL) {
2557 
2558 			    if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w')
2559 				/* if this is a gopher html type */
2560 				result = HTML_GOPHER_URL_TYPE;
2561 			    else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')
2562 				result = TELNET_GOPHER_URL_TYPE;
2563 			    else if (*(cp1 + 1) == '7')
2564 				result = INDEX_GOPHER_URL_TYPE;
2565 			    else
2566 				result = GOPHER_URL_TYPE;
2567 			} else {
2568 			    result = GOPHER_URL_TYPE;
2569 			}
2570 		    }
2571 		    break;
2572 #endif
2573 		case 'W':
2574 		case 'w':
2575 		    if (CompareType(cp, STR_WAIS_URL, LEN_WAIS_URL)) {
2576 			result = WAIS_URL_TYPE;
2577 		    }
2578 		    break;
2579 
2580 		case 'T':
2581 		case 't':
2582 		    if (CompareType(cp, STR_TELNET_URL, LEN_TELNET_URL)) {
2583 			result = TELNET_URL_TYPE;
2584 
2585 		    } else if (CompareType(cp, STR_TN3270_URL, LEN_TN3270_URL)) {
2586 			result = TN3270_URL_TYPE;
2587 		    }
2588 		    break;
2589 
2590 		case 'R':
2591 		case 'r':
2592 		    if (CompareType(cp, STR_RLOGIN_URL, LEN_RLOGIN_URL)) {
2593 			result = RLOGIN_URL_TYPE;
2594 		    }
2595 		    break;
2596 
2597 		case 'C':
2598 		case 'c':
2599 		    if (CompareType(cp, STR_CSO_URL, LEN_CSO_URL)) {
2600 			result = CSO_URL_TYPE;
2601 		    }
2602 		    break;
2603 
2604 		case 'A':
2605 		case 'a':
2606 		    if (CompareType(cp, "afs:", 4)) {
2607 			result = AFS_URL_TYPE;
2608 		    }
2609 		    break;
2610 
2611 		case 'P':
2612 		case 'p':
2613 		    if (CompareType(cp, "prospero:", 9)) {
2614 			result = PROSPERO_URL_TYPE;
2615 		    }
2616 		    break;
2617 		}
2618 	    }
2619 	}
2620 	/*
2621 	 * Check if it is an unknown scheme for which proxying has been set up.
2622 	 */
2623 	if (result == NOT_A_URL_TYPE)
2624 	    result = LYCheckForProxyURL(filename);
2625     }
2626     return result;
2627 }
2628 
2629 /*
2630  * Sometimes it is just expected that curses is on when an alert or other
2631  * statusline message needs to be shown and we are not just dumping
2632  * immediately.  Calling this will 'fix' it, but may not always be appropriate.
2633  * - kw
2634  */
LYFixCursesOn(const char * reason)2635 void LYFixCursesOn(const char *reason)
2636 {
2637     if (dump_output_immediately || LYCursesON)
2638 	return;
2639     if (reason) {
2640 	CTRACE((tfp, "Forcing curses on to %s\n", reason));
2641     }
2642     start_curses();
2643 }
2644 
2645 /*
2646  * Most protocol modules called through HTLoad* expect that curses is on unless
2647  * dump_output_immediately is set, so that statusline messages can be shown.
2648  * Some protocols expect the opposite, namely telnet and friends.  This
2649  * function should be called after the 'physical' URL for accessing addr has
2650  * been established.  It does the right thing to the degree that curses is
2651  * turned on for known problem cases.  In any normal circumstances this should
2652  * never apply, but proxying or rule substitution is not prevented for
2653  * telnet-like URLs, and this 'fix' avoids some crashes that can otherwise
2654  * occur.  - kw
2655  */
LYFixCursesOnForAccess(const char * addr,const char * physical)2656 BOOLEAN LYFixCursesOnForAccess(const char *addr,
2657 			       const char *physical)
2658 {
2659     /*
2660      * If curses is off when maybe it shouldn't...
2661      */
2662     if (!dump_output_immediately && !LYCursesON && physical) {
2663 	char *cp1;
2664 
2665 	/*
2666 	 * If requested resource wants to be accessed with curses off, and
2667 	 * getfile() would indeed have turned curses off for it...
2668 	 */
2669 	if (strstr(addr, "://") != NULL &&
2670 	    (isTELNET_URL(addr) ||
2671 	     isRLOGIN_URL(addr) ||
2672 	     isTN3270_URL(addr) ||
2673 	     (!isGOPHER_URL(addr) &&
2674 	      (cp1 = strchr(addr + 11, '/')) != NULL &&
2675 	      (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')))) {
2676 	    /*
2677 	     * If actual access that will be done is ok with curses off, then
2678 	     * do nothing special, else force curses on.  - kw
2679 	     */
2680 	    if (!isTELNET_URL(physical) &&
2681 		!isRLOGIN_URL(physical) &&
2682 		!isTN3270_URL(physical)) {
2683 		start_curses();
2684 		HTAlert(gettext("Unexpected access protocol for this URL scheme."));
2685 		return TRUE;
2686 	    }
2687 	}
2688     }
2689     return FALSE;
2690 }
2691 
2692 /*
2693  * Determine whether we allow HEAD and related flags for a URL.  - kw
2694  */
LYCanDoHEAD(const char * address)2695 BOOLEAN LYCanDoHEAD(const char *address)
2696 {
2697     char *temp0 = NULL;
2698     int isurl;
2699 
2700     if (!non_empty(address))
2701 	return FALSE;
2702     if (!StrNCmp(address, "http", 4))
2703 	return TRUE;
2704     /* Make copy for is_url() since caller may not care for case changes */
2705     StrAllocCopy(temp0, address);
2706     isurl = is_url(temp0);
2707     if (!isurl) {
2708 	FREE(temp0);
2709 	return FALSE;
2710     }
2711     if (isurl == LYNXCGI_URL_TYPE) {
2712 	FREE(temp0);
2713 #if defined(LYNXCGI_LINKS) && !defined(VMS)
2714 	return TRUE;
2715 #else
2716 	return FALSE;
2717 #endif
2718     }
2719     /*
2720      * The idea of the following is to allow HEAD for news URLs that identify
2721      * single articles, not those that identify ranges of articles or groups or
2722      * a list of groups.  - kw
2723      */
2724     if (isurl == NEWS_URL_TYPE || isurl == NNTP_URL_TYPE) {
2725 	char *temp = HTParse(address, "", PARSE_PATH);
2726 	char *cp = strrchr(temp, '/');
2727 
2728 	if (strchr((cp ? cp : temp), '@') != NULL) {
2729 	    FREE(temp0);
2730 	    FREE(temp);
2731 	    return TRUE;
2732 	}
2733 	if (cp && isdigit(UCH(cp[1])) && strchr(cp, '-') == NULL) {
2734 	    FREE(temp0);
2735 	    FREE(temp);
2736 	    return TRUE;
2737 	}
2738 	FREE(temp);
2739     }
2740 #define ALLOW_PROXY_HEAD
2741 /* If defined, also allow head requests for URLs proxied through the "http" or
2742  * "lynxcgi" protocols, which understand HEAD.  Only the proxy environment
2743  * variables are checked, not the HTRules system.  - kw
2744  */
2745 #ifdef ALLOW_PROXY_HEAD
2746     if (isurl != FILE_URL_TYPE) {
2747 	char *acc_method = HTParse(temp0, "", PARSE_ACCESS);
2748 
2749 	if (non_empty(acc_method)) {
2750 	    char *proxy;
2751 
2752 	    StrAllocCat(acc_method, "_proxy");
2753 	    proxy = LYGetEnv(acc_method);
2754 	    if (proxy && (isHTTP_URL(proxy) ||
2755 			  isLYNXCGI(proxy)) &&
2756 		!override_proxy(temp0)) {
2757 		FREE(temp0);
2758 		FREE(acc_method);
2759 		return TRUE;
2760 	    }
2761 	}
2762 	FREE(acc_method);
2763     }
2764 #endif /* ALLOW_PROXY_HEAD */
2765 
2766     FREE(temp0);
2767     return FALSE;
2768 }
2769 
2770 /*
2771  * Close an input file.
2772  */
LYCloseInput(FILE * fp)2773 BOOLEAN LYCloseInput(FILE *fp)
2774 {
2775     int result = FALSE;
2776 
2777     if (fp != 0) {
2778 	int err = ferror(fp);
2779 	LY_TEMP *p = FindTempfileByFP(fp);
2780 
2781 	fclose(fp);
2782 	if (p != 0) {
2783 	    p->file = 0;
2784 	}
2785 	if (!err) {
2786 	    result = TRUE;
2787 	}
2788     }
2789     return (BOOLEAN) result;
2790 }
2791 
2792 /*
2793  * Close an output file, reporting any problems with writing to it.
2794  */
LYCloseOutput(FILE * fp)2795 BOOLEAN LYCloseOutput(FILE *fp)
2796 {
2797     int result = FALSE;
2798 
2799     if (fp != 0) {
2800 	int err = ferror(fp);
2801 	LY_TEMP *p = FindTempfileByFP(fp);
2802 
2803 	fclose(fp);
2804 	if (p != 0) {
2805 	    p->file = 0;
2806 	}
2807 	if (!err) {
2808 	    result = TRUE;
2809 	}
2810     }
2811     if (!result) {
2812 	HTAlert(CANNOT_WRITE_TO_FILE);
2813     }
2814     return (BOOLEAN) result;
2815 }
2816 
2817 /*
2818  * Test if we'll be able to write a file.  If not, warn the user.
2819  */
LYCanWriteFile(const char * filename)2820 BOOLEAN LYCanWriteFile(const char *filename)
2821 {
2822     BOOLEAN result = FALSE;
2823 
2824     if (LYCloseOutput(fopen(filename, "w"))) {
2825 	if (remove(filename) == 0) {
2826 	    result = TRUE;
2827 	}
2828     } else {
2829 	_statusline(NEW_FILENAME_PROMPT);
2830     }
2831     return result;
2832 }
2833 
2834 /*
2835  * Test if we'll be able to read a file.
2836  */
LYCanReadFile(const char * filename)2837 BOOLEAN LYCanReadFile(const char *filename)
2838 {
2839     FILE *fp;
2840 
2841     if (non_empty(filename)) {
2842 	if ((fp = fopen(filename, "r")) != 0) {
2843 	    return LYCloseInput(fp);
2844 	}
2845     }
2846     return FALSE;
2847 }
2848 
2849 /*
2850  * Remove backslashes from any string.
2851  */
remove_backslashes(char * buf)2852 void remove_backslashes(char *buf)
2853 {
2854     char *cp;
2855 
2856     for (cp = buf; *cp != '\0'; cp++) {
2857 
2858 	if (*cp != '\\') {	/* don't print slashes */
2859 	    *buf = *cp;
2860 	    buf++;
2861 	} else if (*cp == '\\' &&	/* print one slash if there */
2862 		   *(cp + 1) == '\\') {		/* are two in a row         */
2863 	    *buf = *cp;
2864 	    buf++;
2865 	}
2866     }
2867     *buf = '\0';
2868     return;
2869 }
2870 
2871 /*
2872  * Checks to see if the current process is attached via a terminal in the local
2873  * domain.
2874  */
inlocaldomain(void)2875 BOOLEAN inlocaldomain(void)
2876 {
2877     BOOLEAN result = TRUE;
2878 
2879 #ifdef HAVE_UTMP
2880     int n;
2881     FILE *fp;
2882     struct utmp me;
2883     char *cp, *mytty = NULL;
2884 
2885     if ((cp = ttyname(0)))
2886 	mytty = LYLastPathSep(cp);
2887 
2888     result = FALSE;
2889     if (mytty && (fp = fopen(UTMP_FILE, "r")) != NULL) {
2890 	mytty++;
2891 	do {
2892 	    n = (int) fread((char *) &me, sizeof(struct utmp), (size_t) 1, fp);
2893 	} while (n > 0 && !STREQ(me.ut_line, mytty));
2894 	(void) LYCloseInput(fp);
2895 
2896 	if (n > 0) {
2897 	    if (strlen(me.ut_host) > strlen(LYLocalDomain) &&
2898 		STREQ(LYLocalDomain,
2899 		      me.ut_host + strlen(me.ut_host) - strlen(LYLocalDomain))) {
2900 		result = TRUE;
2901 	    }
2902 #ifdef LINUX
2903 	    /* Linux fix to check for local user. J.Cullen 11Jul94              */
2904 	    else if (strlen(me.ut_host) == 0) {
2905 		result = TRUE;
2906 	    }
2907 #endif /* LINUX */
2908 	}
2909 
2910     } else {
2911 	CTRACE((tfp,
2912 		"Could not get ttyname (returned %s) or open UTMP file %s\n",
2913 		NONNULL(cp), UTMP_FILE));
2914     }
2915 #else
2916     CTRACE((tfp, "LYUtils: inlocaldomain() not supported.\n"));
2917 #endif /* HAVE_UTMP */
2918     return (result);
2919 }
2920 
2921 #ifdef HAVE_SIGACTION
2922 /*
2923  * An extended alternative for calling signal(), sets some flags for signal
2924  * handler as we want them if that functionality is available.  (We don't
2925  * return anything from this function since the return value would currently be
2926  * ignored anyway.) - kw
2927  */
LYExtSignal(int sig,LYSigHandlerFunc_t * handler)2928 void LYExtSignal(int sig,
2929 		 LYSigHandlerFunc_t *handler)
2930 {
2931 #ifdef SIGWINCH
2932     /* add more cases to if(condition) if required... */
2933     if (sig == SIGWINCH && LYNonRestartingSIGWINCH) {
2934 	struct sigaction act;
2935 
2936 	act.sa_handler = handler;
2937 	sigemptyset(&act.sa_mask);
2938 	act.sa_flags = 0;
2939 	sigaction(sig, &act, NULL);
2940     } else
2941 #endif /* defined(SIGWINCH) */
2942 	signal(sig, handler);
2943 }
2944 #endif /* HAVE_SIGACTION */
2945 
2946 #if defined(SIGTSTP) && !defined(USE_SLANG)
2947 #ifdef HAVE_SIGACTION
2948 /*
2949  * For switching a signal's handling between SIG_DFL and something (possibly)
2950  * different that may have been set up by lynx code or e.g. by curses library.
2951  * Uses sigaction to preserve / restore as much state as possible.
2952  *
2953  * Second arg is where to save or restore from.
2954  *
2955  * Third arg to_dfl specifies what to do:
2956  *	1	Save current state in where, set handling to SIG_DFL
2957  *	0	Restore current state to previously saved one in where
2958  *
2959  * Currently only used for SIGTSTP without SLANG, to prevent (n)curses signal
2960  * handler from running while lynx is waiting in system() for an interactive
2961  * command like an editor.  - kw
2962  */
LYToggleSigDfl(int sig,struct sigaction * where,int to_dfl)2963 static BOOLEAN LYToggleSigDfl(int sig,
2964 			      struct sigaction *where,
2965 			      int to_dfl)
2966 {
2967     int rv = -1;
2968     struct sigaction oact;
2969 
2970     if (to_dfl == 1) {
2971 	rv = sigaction(sig, NULL, &oact);
2972 	if (rv == 0) {
2973 	    if (oact.sa_handler != SIG_DFL) {
2974 		oact.sa_handler = SIG_DFL;
2975 		rv = sigaction(sig, &oact, where);
2976 	    } else if (where) {
2977 		memcpy(where, &oact, sizeof(oact));
2978 		rv = 0;
2979 	    }
2980 	}
2981     } else {
2982 	rv = sigaction(sig, where, NULL);
2983     }
2984     if (rv != 0) {
2985 	CTRACE((tfp, "Error in LYToggleSigDfl: %s\n", LYStrerror(errno)));
2986 	return FALSE;
2987     } else
2988 	return TRUE;
2989 }
2990 #endif /* HAVE_SIGACTION */
2991 #endif /* SIGTSTP && !USE_SLANG */
2992 
2993 /**************
2994  * This bit of code catches window size change signals
2995  */
2996 
2997 #ifdef HAVE_SYS_IOCTL_H
2998 #include <sys/ioctl.h>
2999 #endif
3000 
3001 /* For systems that have both, but both can't be included, duh (or neither) */
3002 /* FIXME: this whole chunk may be redundant */
3003 #ifdef TERMIO_AND_CURSES
3004 # ifdef TERMIO_AND_TERMIOS
3005 #  include <termio.h>
3006 # else
3007 #  ifdef HAVE_TERMIOS_H
3008 #   include <termios.h>
3009 #  else
3010 #   ifdef HAVE_TERMIO_H
3011 #    include <termio.h>
3012 #   endif /* HAVE_TERMIO_H */
3013 #  endif /* HAVE_TERMIOS_H */
3014 # endif	/* TERMIO_AND_TERMIOS */
3015 #endif /* TERMIO_AND_CURSES */
3016 
size_change(int sig GCC_UNUSED)3017 void size_change(int sig GCC_UNUSED)
3018 {
3019     int old_lines = LYlines;
3020     int old_cols = LYcols;
3021 
3022 #ifdef USE_SLANG
3023 #if defined(VMS) || defined(UNIX)
3024     SLtt_get_screen_size();
3025 #endif /* VMS || UNIX */
3026     LYlines = SLtt_Screen_Rows;
3027     LYcols = SLtt_Screen_Cols;
3028 #ifdef SLANG_MBCS_HACK
3029     PHYSICAL_SLtt_Screen_Cols = LYcols;
3030 #ifdef SLANG_NO_LIMIT		/* define this if slang has been fixed */
3031     SLtt_Screen_Cols = LYcolLimit * 6;
3032 #else
3033     /* Needs to be limited: fixed buffer bugs in slang can cause crash,
3034        see slang's SLtt_smart_puts - kw */
3035     SLtt_Screen_Cols = HTMIN(LYcolLimit * 6, 255);
3036 #endif
3037 #endif /* SLANG_MBCS_HACK */
3038     if (sig == 0)
3039 	/*
3040 	 * Called from start_curses().
3041 	 */
3042 	return;
3043 #else /* Curses: */
3044 #ifdef HAVE_SIZECHANGE
3045 #ifdef TIOCGSIZE
3046     struct ttysize win;
3047 
3048 #else
3049 #ifdef TIOCGWINSZ
3050     struct winsize win;
3051 #endif /* TIOCGWINSZ */
3052 #endif /* TIOCGSIZE */
3053 
3054 #ifdef TIOCGSIZE
3055     if (ioctl(0, TIOCGSIZE, &win) == 0) {
3056 	if (win.ts_lines != 0) {
3057 	    LYlines = win.ts_lines;
3058 	}
3059 	if (win.ts_cols != 0) {
3060 	    LYcols = win.ts_cols;
3061 	}
3062     }
3063 #else
3064 #ifdef TIOCGWINSZ
3065     if (ioctl(0, (long) TIOCGWINSZ, &win) == 0) {
3066 	if (win.ws_row != 0) {
3067 	    LYlines = win.ws_row;
3068 	}
3069 	if (win.ws_col != 0) {
3070 	    LYcols = win.ws_col;
3071 	}
3072     }
3073 #endif /* TIOCGWINSZ */
3074 #endif /* TIOCGSIZE */
3075 #endif /* HAVE_SIZECHANGE */
3076 
3077 #ifdef __EMX__
3078     {
3079 	int scrsize[2];
3080 
3081 	_scrsize(scrsize);
3082 	LYcols = scrsize[0];
3083 	LYlines = scrsize[1];
3084     }
3085 #endif
3086 
3087     if (LYlines <= 0)
3088 	LYlines = DFT_ROWS;
3089     if (LYcols <= 0)
3090 	LYcols = DFT_COLS;
3091 #endif /* USE_SLANG */
3092 
3093     /*
3094      * Check if the screen size has actually changed.  - AJL
3095      */
3096     if (LYlines != old_lines || LYcols != old_cols) {
3097 	recent_sizechange = TRUE;
3098 	CTRACE((tfp, "Window size changed from (%d,%d) to (%d,%d)\n",
3099 		old_lines, old_cols, LYlines, LYcols));
3100 #if defined(CAN_SWITCH_DISPLAY_CHARSET) && defined(CAN_AUTODETECT_DISPLAY_CHARSET)
3101 	/* May need to reload the font due to different char-box size */
3102 	if (current_char_set != auto_display_charset)
3103 	    Switch_Display_Charset(current_char_set, SWITCH_DISPLAY_CHARSET_RESIZE);
3104 #endif
3105     }
3106 #ifdef SIGWINCH
3107     LYExtSignal(SIGWINCH, size_change);
3108 #endif /* SIGWINCH */
3109 
3110     return;
3111 }
3112 
3113 /*
3114  * Utility for freeing the list of previous suggested filenames.  - FM
3115  */
HTSugFilenames_free(void)3116 void HTSugFilenames_free(void)
3117 {
3118     LYFreeStringList(sug_filenames);
3119     sug_filenames = NULL;
3120 }
3121 
3122 /*
3123  * Utility for listing suggested filenames, making any repeated filenames the
3124  * most current in the list.  - FM
3125  */
HTAddSugFilename(char * fname)3126 void HTAddSugFilename(char *fname)
3127 {
3128     char *tmp = NULL;
3129     char *old;
3130     HTList *cur;
3131 
3132     if (!non_empty(fname))
3133 	return;
3134 
3135     StrAllocCopy(tmp, fname);
3136 
3137     if (!sug_filenames) {
3138 	sug_filenames = HTList_new();
3139 #ifdef LY_FIND_LEAKS
3140 	atexit(HTSugFilenames_free);
3141 #endif
3142 	HTList_addObject(sug_filenames, tmp);
3143 	return;
3144     }
3145 
3146     cur = sug_filenames;
3147     while (NULL != (old = (char *) HTList_nextObject(cur))) {
3148 	if (!strcmp(old, tmp)) {
3149 	    HTList_removeObject(sug_filenames, old);
3150 	    FREE(old);
3151 	    break;
3152 	}
3153     }
3154     HTList_addObject(sug_filenames, tmp);
3155 
3156     return;
3157 }
3158 
3159 /*
3160  * CHANGE_SUG_FILENAME -- Foteos Macrides 29-Dec-1993 Upgraded for use with
3161  * Lynx2.2 - FM 17-Jan-1994
3162  */
change_sug_filename(char * fname)3163 void change_sug_filename(char *fname)
3164 {
3165     const char *cp2;
3166     char *temp = 0, *cp, *cp1, *end;
3167 
3168 #ifdef VMS
3169     char *dot;
3170     int j, k;
3171 #endif /* VMS */
3172 
3173     /*
3174      * Establish the current end of fname.
3175      */
3176     end = fname + strlen(fname);
3177 
3178     /*
3179      * Unescape fname.
3180      */
3181     HTUnEscape(fname);
3182 
3183     /*
3184      * Rename any temporary files.
3185      */
3186     cp2 = wwwName(lynx_temp_space);
3187     if (LYIsHtmlSep(*cp2)) {
3188 	HTSprintf0(&temp, "file://localhost%s" PID_FMT, cp2, GETPID());
3189     } else {
3190 	HTSprintf0(&temp, "file://localhost/%s" PID_FMT, cp2, GETPID());
3191     }
3192     if (!StrNCmp(fname, temp, strlen(temp))) {
3193 	if ((cp = strrchr(fname, '.')) != 0) {
3194 	    if (strlen(cp) > (strlen(temp) - 4))
3195 		cp = NULL;
3196 	}
3197 	StrAllocCopy(temp, NonNull(cp));
3198 	sprintf(fname, "temp%.*s", LY_MAXPATH - 10, temp);
3199     }
3200     FREE(temp);
3201 
3202     if (fname[strlen(fname) - 1] == '/')
3203 	/*
3204 	 * Hmm...  we have a directory name.  It is annoying to see a
3205 	 * scheme+host+path name as a suggested one, let's remove the
3206 	 * last_slash and go ahead like we have a file name.  - LP
3207 	 */
3208 	fname[strlen(fname) - 1] = '\0';
3209 
3210     /*
3211      * Remove everything up the the last_slash if there is one.
3212      */
3213     if ((cp = strrchr(fname, '/')) != NULL && strlen(cp) > 1) {
3214 	cp1 = fname;
3215 	/*
3216 	 * Go past the slash.
3217 	 */
3218 	cp++;
3219 	for (; *cp != '\0'; cp++, cp1++) {
3220 	    *cp1 = *cp;
3221 	}
3222 	*cp1 = '\0';
3223     }
3224 #ifdef _WINDOWS			/* 1998/05/05 (Tue) 10:08:05 */
3225     if ((cp = strrchr(fname, '=')) != NULL && strlen(cp) > 1) {
3226 	cp1 = fname;
3227 	/*
3228 	 * Go past the '='.
3229 	 */
3230 	cp++;
3231 	for (; *cp != '\0'; cp++, cp1++) {
3232 	    *cp1 = *cp;
3233 	}
3234 	*cp1 = '\0';
3235     }
3236 #endif
3237 
3238     /*
3239      * Trim off date-size suffix, if present.
3240      */
3241     if ((*(end - 1) == ']') && ((cp = strrchr(fname, '[')) != NULL) &&
3242 	(cp > fname) && *(--cp) == ' ') {
3243 	while (*cp == ' ') {
3244 	    *(cp--) = '\0';
3245 	}
3246     }
3247 #ifdef VMS
3248     /*
3249      * Trim off VMS device and/or directory specs, if present.
3250      */
3251     if ((cp = strchr(fname, '[')) != NULL &&
3252 	(cp1 = strrchr(cp, ']')) != NULL && strlen(cp1) > 1) {
3253 	cp1++;
3254 	for (cp = fname; *cp1 != '\0'; cp1++) {
3255 	    *(cp++) = *cp1;
3256 	}
3257 	*cp = '\0';
3258     }
3259     /*
3260      * Replace illegal or problem characters.
3261      */
3262     dot = fname + strlen(fname);
3263     for (cp = fname; cp < dot; cp++) {
3264 	/*
3265 	 * Replace with underscores.
3266 	 */
3267 	if (*cp == ' ' || *cp == '/' || *cp == ':' ||
3268 	    *cp == '[' || *cp == ']' || *cp == '&') {
3269 	    *cp = '_';
3270 	    /*
3271 	     * Replace with dashes.
3272 	     */
3273 	} else if (*cp == '!' || *cp == '?' || *cp == '\'' ||
3274 		   *cp == ',' || *cp == ':' || *cp == '"' ||
3275 		   *cp == '+' || *cp == '@' || *cp == '\\' ||
3276 		   *cp == '(' || *cp == ')' || *cp == '=' ||
3277 		   *cp == '<' || *cp == '>' || *cp == '#' ||
3278 		   *cp == '%' || *cp == '*' || *cp == '`' ||
3279 		   *cp == '~' || *cp == '^' || *cp == '|' ||
3280 		   *cp < ' ' || (UCH(*cp)) > 126) {
3281 	    *cp = '-';
3282 	}
3283     }
3284 
3285     /*
3286      * Collapse any serial underscores.
3287      */
3288     cp = fname + 1;
3289     j = 0;
3290     while (cp < dot) {
3291 	if (fname[j] == '_' && *cp == '_') {
3292 	    cp++;
3293 	} else {
3294 	    fname[++j] = *cp++;
3295 	}
3296     }
3297     fname[++j] = '\0';
3298 
3299     /*
3300      * Collapse any serial dashes.
3301      */
3302     dot = fname + (strlen(fname));
3303     cp = fname + 1;
3304     j = 0;
3305     while (cp < dot) {
3306 	if (fname[j] == '-' && *cp == '-') {
3307 	    cp++;
3308 	} else {
3309 	    fname[++j] = *cp++;
3310 	}
3311     }
3312     fname[++j] = '\0';
3313 
3314     /*
3315      * Trim any trailing or leading underscores or dashes.
3316      */
3317     cp = fname + (strlen(fname)) - 1;
3318     while (*cp == '_' || *cp == '-') {
3319 	*cp-- = '\0';
3320     }
3321     if (fname[0] == '_' || fname[0] == '-') {
3322 	dot = fname + (strlen(fname));
3323 	cp = fname;
3324 	while ((*cp == '_' || *cp == '-') && cp < dot) {
3325 	    cp++;
3326 	}
3327 	j = 0;
3328 	while (cp < dot) {
3329 	    fname[j++] = *cp++;
3330 	}
3331 	fname[j] = '\0';
3332     }
3333 
3334     /*
3335      * Replace all but the last period with _s, or second to last if last is
3336      * followed by a terminal Z or z, or GZ or gz,
3337      * e.g., convert foo.tar.Z to foo.tar_Z
3338      * or, convert foo.tar.gz to foo.tar-gz
3339      */
3340     j = strlen(fname) - 1;
3341     if ((dot = strrchr(fname, '.')) != NULL) {
3342 	if (TOUPPER(fname[j]) == 'Z') {
3343 	    if ((fname[j - 1] == '.') &&
3344 		(((cp = strchr(fname, '.')) != NULL) && cp < dot)) {
3345 		*dot = '_';
3346 		dot = strrchr(fname, '.');
3347 	    } else if (((TOUPPER(fname[j - 1]) == 'G') &&
3348 			fname[j - 2] == '.') &&
3349 		       (((cp = strchr(fname, '.')) != NULL) && cp < dot)) {
3350 		*dot = '-';
3351 		dot = strrchr(fname, '.');
3352 	    }
3353 	}
3354 	cp = fname;
3355 	while ((cp = strchr(cp, '.')) != NULL && cp < dot) {
3356 	    *cp = '_';
3357 	}
3358 
3359 	/*
3360 	 * But if the root is > 39 characters, move the period appropriately to
3361 	 * the left.
3362 	 */
3363 	while (dot - fname > 39) {
3364 	    *dot = '\0';
3365 	    if ((cp = strrchr(fname, '_')) != NULL) {
3366 		*cp = '.';
3367 		*dot = '_';
3368 	    } else if ((cp = strrchr(fname, '-')) != NULL) {
3369 		*cp = '.';
3370 		*dot = '_';
3371 	    } else if (*(dot + 1) == '\0') {
3372 		j = strlen(fname);
3373 		while (j > 39) {
3374 		    fname[j] = fname[j - 1];
3375 		    j--;
3376 		}
3377 		fname[j] = '.';
3378 	    } else {
3379 		*dot = '.';
3380 		j = 39;
3381 		k = 0;
3382 		while (dot[k] != '\0') {
3383 		    fname[j++] = dot[k++];
3384 		}
3385 		fname[j] = '\0';
3386 	    }
3387 	    dot = strrchr(fname, '.');
3388 	}
3389 
3390 	/*
3391 	 * Make sure the extension is < 40 characters.
3392 	 */
3393 	if ((fname + strlen(fname) - dot) > 39) {
3394 	    *(dot + 40) = '\0';
3395 	}
3396 
3397 	/*
3398 	 * Trim trailing dashes or underscores.
3399 	 */
3400 	j = (strlen(fname) - 1);
3401 	while (fname[j] == '_' || fname[j] == '-') {
3402 	    fname[j--] = '\0';
3403 	}
3404     } else {
3405 	/*
3406 	 * No period, so put one on the end, or after the 39th character,
3407 	 * trimming trailing dashes or underscores.
3408 	 */
3409 	if (strlen(fname) > 39) {
3410 	    fname[39] = '\0';
3411 	}
3412 	j = (strlen(fname) - 1);
3413 	while ((fname[j] == '_') || (fname[j] == '-')) {
3414 	    j--;
3415 	}
3416 	fname[++j] = '.';
3417 	fname[++j] = '\0';
3418     }
3419 
3420 #else /* Not VMS (UNIX): */
3421 
3422     /*
3423      * Replace problem characters.
3424      */
3425     for (cp = fname; *cp != '\0'; cp++) {
3426 	switch (*cp) {
3427 	case '\'':
3428 	case '"':
3429 	case '/':
3430 	case ' ':
3431 	    *cp = '-';
3432 	}
3433     }
3434 #endif /* VMS (UNIX) */
3435 
3436     /*
3437      * Make sure the rest of the original string in nulled.
3438      */
3439     cp = fname + strlen(fname);
3440     while (cp < end) {
3441 	*cp++ = '\0';
3442     }
3443 
3444     return;
3445 }
3446 
3447 /*
3448  * Construct a temporary-filename.  Assumes result is LY_MAXPATH chars long.
3449  */
fmt_tempname(char * result,const char * prefix,const char * suffix)3450 static int fmt_tempname(char *result,
3451 			const char *prefix,
3452 			const char *suffix)
3453 {
3454     int code;
3455 
3456 #ifdef HAVE_RAND_TEMPNAME
3457 #define SIZE_TEMPNAME ((MAX_TEMPNAME / BITS_PER_CHAR) + 1)
3458     static BOOL first = TRUE;
3459     static int names_used = 0;
3460     static unsigned char used_tempname[SIZE_TEMPNAME];
3461     unsigned offset, mask;
3462 #endif
3463     static unsigned counter;
3464     char leaf[LY_MAXPATH];
3465 
3466     if (prefix == 0)
3467 	prefix = "";
3468     if (suffix == 0)
3469 	suffix = "";
3470     /*
3471      * Prefer a random value rather than a counter.
3472      */
3473 #ifdef HAVE_RAND_TEMPNAME
3474     if (first) {
3475 	lynx_srand((unsigned) ((long) time((time_t *) NULL) + (long) result));
3476 	first = FALSE;
3477     }
3478 
3479     /* We don't really need all of the bits from rand().  The high-order bits
3480      * are the more-random portion in any case, but limiting the width of the
3481      * generated name is done partly to avoid problems on systems that may not
3482      * support long filenames.
3483      */
3484     counter = MAX_TEMPNAME;
3485     if (names_used < MAX_TEMPNAME) {
3486 	long get_rand = lynx_rand();
3487 	long max_rand = LYNX_RAND_MAX;
3488 
3489 	counter = (unsigned) (((float) MAX_TEMPNAME * (float) get_rand) /
3490 			      (float) max_rand + 1);
3491 	/*
3492 	 * Avoid reusing a temporary name, since there are places in the code
3493 	 * which can refer to a temporary filename even after it has been
3494 	 * closed and removed from the filesystem.
3495 	 */
3496 	do {
3497 	    counter %= MAX_TEMPNAME;
3498 	    offset = counter / BITS_PER_CHAR;
3499 	    mask = (unsigned) (1 << (counter % BITS_PER_CHAR));
3500 	    if ((used_tempname[offset] & mask) == 0) {
3501 		names_used++;
3502 		used_tempname[offset] |= UCH(mask);
3503 		break;
3504 	    }
3505 	} while ((used_tempname[offset] & mask) == 0);
3506     }
3507     if (names_used >= MAX_TEMPNAME)
3508 	HTAlert(gettext("Too many tempfiles"));
3509 #else
3510     counter++;
3511 #endif
3512 
3513 #ifdef FNAMES_8_3
3514     /*
3515      * The 'lynx_temp_space' string ends with a '/' or '\\', so we only have to
3516      * limit the length of the leaf.  As received (e.g., from HTCompressed),
3517      * the suffix may contain more than a ".htm", e.g., "-txt.gz", so we trim
3518      * off from the filename portion to make room.
3519      */
3520     sprintf(leaf, PID_FMT PID_FMT, counter, GETPID());
3521     if (strlen(leaf) > 8)
3522 	leaf[8] = 0;
3523     if (strlen(suffix) > 4 || *suffix != '.') {
3524 	const char *tail = strchr(suffix, '.');
3525 
3526 	if (tail == 0)
3527 	    tail = suffix + strlen(suffix);
3528 	if (8 - (tail - suffix) >= 0)
3529 	    leaf[8 - (tail - suffix)] = 0;
3530     }
3531     strcat(leaf, suffix);
3532 #else
3533     sprintf(leaf, "L" PID_FMT "-%uTMP%s", GETPID(), counter, suffix);
3534 #endif
3535     /*
3536      * Someone could have configured the temporary pathname to be too long.
3537      */
3538     if ((strlen(prefix) + strlen(leaf)) < LY_MAXPATH) {
3539 	sprintf(result, "%s%s", prefix, leaf);
3540 	code = TRUE;
3541     } else {
3542 	sprintf(result, "%.*s", LY_MAXPATH - 1, leaf);
3543 	code = FALSE;
3544     }
3545     CTRACE((tfp, "-> '%s'\n", result));
3546     return (code);
3547 }
3548 
3549 /*
3550  * Convert 4, 6, 2, 8 to left, right, down, up, etc.
3551  */
number2arrows(int number)3552 int number2arrows(int number)
3553 {
3554     switch (number) {
3555     case '1':
3556 	number = END_KEY;
3557 	break;
3558     case '2':
3559 	number = DNARROW;
3560 	break;
3561     case '3':
3562 	number = PGDOWN;
3563 	break;
3564     case '4':
3565 	number = LTARROW;
3566 	break;
3567     case '5':
3568 	number = DO_NOTHING;
3569 	break;
3570     case '6':
3571 	number = RTARROW;
3572 	break;
3573     case '7':
3574 	number = HOME;
3575 	break;
3576     case '8':
3577 	number = UPARROW;
3578 	break;
3579     case '9':
3580 	number = PGUP;
3581 	break;
3582     }
3583 
3584     return (number);
3585 }
3586 
3587 /*
3588  * parse_restrictions takes a string of comma-separated restrictions and sets
3589  * the corresponding flags to restrict the facilities available.
3590  */
3591 /* The first two are special:  we want to record whether "default" or "all"
3592  * restrictions were applied, in addition to the detailed effects of those
3593  * options.  - kw
3594  */
3595 /* skip the special flags when processing "all" and "default": */
3596 #define N_SPECIAL_RESTRICT_OPTIONS 2
3597 /* *INDENT-OFF* */
3598 static const struct {
3599     const char *name;
3600     BOOLEAN *flag;
3601     BOOLEAN can;
3602 } restrictions[] = {
3603     { "default",	&had_restrictions_default, TRUE },
3604     { "all",		&had_restrictions_all,	TRUE },
3605     { "inside_telnet",	&no_inside_telnet,	CAN_ANONYMOUS_INSIDE_DOMAIN_TELNET },
3606     { "outside_telnet",	&no_outside_telnet,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_TELNET },
3607     { "telnet_port",	&no_telnet_port,	CAN_ANONYMOUS_GOTO_TELNET_PORT },
3608     { "inside_ftp",	&no_inside_ftp,		CAN_ANONYMOUS_INSIDE_DOMAIN_FTP },
3609     { "outside_ftp",	&no_outside_ftp,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_FTP },
3610     { "inside_rlogin",	&no_inside_rlogin,	CAN_ANONYMOUS_INSIDE_DOMAIN_RLOGIN },
3611     { "outside_rlogin",	&no_outside_rlogin,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_RLOGIN },
3612     { "suspend",	&no_suspend,		FALSE },
3613     { "editor",		&no_editor,		FALSE },
3614     { "shell",		&no_shell,		FALSE },
3615     { "bookmark",	&no_bookmark,		FALSE },
3616     { "multibook",	&no_multibook,		FALSE },
3617     { "bookmark_exec",	&no_bookmark_exec,	FALSE },
3618     { "option_save",	&no_option_save,	FALSE },
3619     { "print",		&no_print,		CAN_ANONYMOUS_PRINT },
3620     { "download",	&no_download,		FALSE },
3621     { "disk_save",	&no_disk_save,		FALSE },
3622 #if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
3623     { "exec",		&no_exec,		LOCAL_EXECUTION_LINKS_ALWAYS_OFF_FOR_ANONYMOUS },
3624 #endif
3625     { "lynxcgi",	&no_lynxcgi,		FALSE },
3626     { "exec_frozen",	&exec_frozen,		FALSE },
3627     { "goto",		&no_goto,		CAN_ANONYMOUS_GOTO },
3628     { "jump",		&no_jump,		CAN_ANONYMOUS_JUMP },
3629     { "file_url",	&no_file_url,		FALSE },
3630 #ifndef DISABLE_NEWS
3631     { "news_post",	&no_newspost,		FALSE },
3632     { "inside_news",	&no_inside_news,	CAN_ANONYMOUS_INSIDE_DOMAIN_READ_NEWS },
3633     { "outside_news",	&no_outside_news,	CAN_ANONYMOUS_OUTSIDE_DOMAIN_READ_NEWS },
3634 #endif
3635     { "mail",		&no_mail,		CAN_ANONYMOUS_MAIL },
3636     { "dotfiles",	&no_dotfiles,		FALSE },
3637     { "useragent",	&no_useragent,		FALSE },
3638 #ifdef SUPPORT_CHDIR
3639     { "chdir",		&no_chdir,		FALSE },
3640 #endif
3641 #ifdef DIRED_SUPPORT
3642     { "dired_support",	&no_dired_support,	FALSE },
3643 #ifdef OK_PERMIT
3644     { "change_exec_perms", &no_change_exec_perms, FALSE },
3645 #endif /* OK_PERMIT */
3646 #endif /* DIRED_SUPPORT */
3647 #ifdef USE_EXTERNALS
3648     { "externals",	&no_externals,		FALSE },
3649 #endif
3650     { "lynxcfg_info",	&no_lynxcfg_info,	CAN_ANONYMOUS_VIEW_LYNXCFG_INFO },
3651 #ifndef NO_CONFIG_INFO
3652     { "lynxcfg_xinfo",	&no_lynxcfg_xinfo,	CAN_ANONYMOUS_VIEW_LYNXCFG_EXTENDED_INFO },
3653 #ifdef HAVE_CONFIG_H
3654     { "compileopts_info", &no_compileopts_info,	CAN_ANONYMOUS_VIEW_COMPILEOPTS_INFO },
3655 #endif
3656 #endif
3657     /* put "goto" restrictions on the end, since they are a refinement */
3658 #ifndef DISABLE_BIBP
3659     { "goto_bibp",	&no_goto_bibp,		CAN_ANONYMOUS_GOTO_BIBP	},
3660 #endif
3661 #ifdef HAVE_CONFIG_H
3662 #ifndef NO_CONFIG_INFO
3663     { "goto_configinfo", &no_goto_configinfo,	CAN_ANONYMOUS_GOTO_CONFIGINFO },
3664 #endif
3665 #endif
3666     { "goto_cso",	&no_goto_cso,		CAN_ANONYMOUS_GOTO_CSO },
3667     { "goto_file",	&no_goto_file,		CAN_ANONYMOUS_GOTO_FILE },
3668 #ifndef DISABLE_FINGER
3669     { "goto_finger",	&no_goto_finger,	CAN_ANONYMOUS_GOTO_FINGER },
3670 #endif
3671     { "goto_ftp",	&no_goto_ftp,		CAN_ANONYMOUS_GOTO_FTP },
3672 #ifndef DISABLE_GOPHER
3673     { "goto_gopher",	&no_goto_gopher,	CAN_ANONYMOUS_GOTO_GOPHER },
3674 #endif
3675     { "goto_http",	&no_goto_http,		CAN_ANONYMOUS_GOTO_HTTP },
3676     { "goto_https",	&no_goto_https,		CAN_ANONYMOUS_GOTO_HTTPS },
3677     { "goto_lynxcgi",	&no_goto_lynxcgi,	CAN_ANONYMOUS_GOTO_LYNXCGI },
3678     { "goto_lynxexec",	&no_goto_lynxexec,	CAN_ANONYMOUS_GOTO_LYNXEXEC },
3679     { "goto_lynxprog",	&no_goto_lynxprog,	CAN_ANONYMOUS_GOTO_LYNXPROG },
3680     { "goto_mailto",	&no_goto_mailto,	CAN_ANONYMOUS_GOTO_MAILTO },
3681 #ifndef DISABLE_NEWS
3682     { "goto_news",	&no_goto_news,		CAN_ANONYMOUS_GOTO_NEWS },
3683     { "goto_nntp",	&no_goto_nntp,		CAN_ANONYMOUS_GOTO_NNTP },
3684 #endif
3685     { "goto_rlogin",	&no_goto_rlogin,	CAN_ANONYMOUS_GOTO_RLOGIN },
3686 #ifndef DISABLE_NEWS
3687     { "goto_snews",	&no_goto_snews,		CAN_ANONYMOUS_GOTO_SNEWS },
3688 #endif
3689     { "goto_telnet",	&no_goto_telnet,	CAN_ANONYMOUS_GOTO_TELNET },
3690     { "goto_tn3270",	&no_goto_tn3270,	CAN_ANONYMOUS_GOTO_TN3270 },
3691     { "goto_wais",	&no_goto_wais,		CAN_ANONYMOUS_GOTO_WAIS },
3692 };
3693 /* *INDENT-ON* */
3694 
3695 /* This will make no difference between '-' and '_'.  It does only in/equality
3696  * compare.  It assumes that p2 can't contain dashes, but p1 can.  This
3697  * function is also used (if macro OPTNAME_ALLOW_DASHES doesn't have value of
3698  * zero) for compare of commandline options -VH
3699  */
strn_dash_equ(const char * p1,const char * p2,int len)3700 BOOL strn_dash_equ(const char *p1,
3701 		   const char *p2,
3702 		   int len)
3703 {
3704     while (len--) {
3705 	if (!*p2)
3706 	    return 0;		/* canonical name is shorter */
3707 	switch (*p1) {
3708 	case 0:
3709 	    return 0;
3710 	case '-':
3711 	case '_':
3712 	    if (*p2 != '_')
3713 		return 0;
3714 	    else
3715 		break;
3716 	default:
3717 	    if (*p1 != *p2)
3718 		return 0;
3719 	}
3720 	++p1;
3721 	++p2;
3722     }
3723     return 1;
3724 }
3725 
3726 /* Uncomment following lines to allow only exact string matching */
3727 /* #define RESTRICT_NM_ALLOW_DASHES 0 */
3728 
3729 #ifndef RESTRICT_NM_ALLOW_DASHES
3730 # define RESTRICT_NM_ALLOW_DASHES 1
3731 #endif
3732 
3733 #if RESTRICT_NM_ALLOW_DASHES
3734 #	define RESTRICT_NM_EQU(a,b,len) strn_dash_equ(a,b,len)
3735 #else
3736 #	define RESTRICT_NM_EQU(a,b,len) STRNEQ(a,b,len)
3737 #endif
3738 
3739 /*
3740  * Returns the inx'th name from the restrictions table, or null if inx is
3741  * out of range.
3742  */
index_to_restriction(unsigned inx)3743 const char *index_to_restriction(unsigned inx)
3744 {
3745     if (inx < TABLESIZE(restrictions))
3746 	return restrictions[inx].name;
3747     return NULL;
3748 }
3749 
3750 /*
3751  * Returns the value TRUE/FALSE of a given restriction, or -1 if it is not
3752  * one that we recognize.
3753  */
find_restriction(const char * name,int len)3754 int find_restriction(const char *name,
3755 		     int len)
3756 {
3757     unsigned i;
3758 
3759     if (len < 0)
3760 	len = (int) strlen(name);
3761     for (i = 0; i < TABLESIZE(restrictions); i++) {
3762 	if (RESTRICT_NM_EQU(name, restrictions[i].name, len)) {
3763 	    return (*restrictions[i].flag);
3764 	}
3765     }
3766     return -1;
3767 }
3768 
parse_restrictions(const char * s)3769 void parse_restrictions(const char *s)
3770 {
3771     const char *p;
3772     const char *word;
3773     unsigned i;
3774     BOOLEAN found;
3775 
3776     p = s;
3777     while (*p) {
3778 	p = LYSkipCBlanks(p);
3779 	if (*p == '\0')
3780 	    break;
3781 	word = p;
3782 	while (*p != ',' && *p != '\0')
3783 	    p++;
3784 
3785 	found = FALSE;
3786 	if (RESTRICT_NM_EQU(word, "all", (int) (p - word))) {
3787 	    found = TRUE;
3788 	    for (i = N_SPECIAL_RESTRICT_OPTIONS;
3789 		 i < TABLESIZE(restrictions);
3790 		 i++)
3791 		*(restrictions[i].flag) = TRUE;
3792 	} else if (RESTRICT_NM_EQU(word, "default", (int) (p - word))) {
3793 	    found = TRUE;
3794 	    for (i = N_SPECIAL_RESTRICT_OPTIONS;
3795 		 i < TABLESIZE(restrictions);
3796 		 i++)
3797 		*(restrictions[i].flag) = (BOOLEAN) !restrictions[i].can;
3798 	} else {
3799 	    for (i = 0; i < TABLESIZE(restrictions); i++) {
3800 		if (RESTRICT_NM_EQU(word, restrictions[i].name, (int) (p - word))) {
3801 		    *(restrictions[i].flag) = TRUE;
3802 		    found = TRUE;
3803 		    break;
3804 		}
3805 	    }
3806 	}
3807 	if (!found) {
3808 	    printf("%s: %.*s\n", gettext("unknown restriction"),
3809 		   (int) (p - word), word);
3810 	    exit_immediately(EXIT_FAILURE);
3811 	}
3812 	if (*p)
3813 	    p++;
3814     }
3815 
3816     /*
3817      * If shell is restricted, set restrictions on related topics.
3818      */
3819     if (no_shell) {
3820 	no_goto_lynxexec = TRUE;
3821 	no_goto_lynxprog = TRUE;
3822 	no_goto_lynxcgi = TRUE;
3823 #ifdef EXEC_LINKS
3824 	local_exec_on_local_files = TRUE;
3825 #endif
3826     }
3827 }
3828 
print_restrictions_to_fd(FILE * fp)3829 void print_restrictions_to_fd(FILE *fp)
3830 {
3831     unsigned i, count = 0;
3832 
3833     for (i = 0; i < TABLESIZE(restrictions); i++) {
3834 	if (*(restrictions[i].flag) == TRUE) {
3835 	    count++;
3836 	}
3837     }
3838     if (!count) {
3839 	fprintf(fp, gettext("No restrictions set.\n"));
3840 	return;
3841     }
3842     fprintf(fp, gettext("Restrictions set:\n"));
3843     for (i = 0; i < TABLESIZE(restrictions); i++) {
3844 	if (*(restrictions[i].flag) == TRUE) {
3845 	    /* if "goto" is restricted, don't bother tell about its
3846 	     * refinements
3847 	     */
3848 	    if (StrNCmp(restrictions[i].name, "goto_", 5)
3849 		|| !no_goto)
3850 		fprintf(fp, "   %s\n", restrictions[i].name);
3851 	}
3852     }
3853 }
3854 
3855 #ifdef VMS
3856 #include <jpidef.h>
3857 #include <maildef.h>
3858 #include <starlet.h>
3859 
3860 typedef struct _VMSMailItemList {
3861     short buffer_length;
3862     short item_code;
3863     void *buffer_address;
3864     long *return_length_address;
3865 } VMSMailItemList;
3866 
LYCheckMail(void)3867 void LYCheckMail(void)
3868 {
3869     static BOOL firsttime = TRUE, failure = FALSE;
3870     static char user[13], dir[252];
3871     static long userlen = 0, dirlen;
3872     static time_t lastcheck = 0;
3873     time_t now;
3874     static short new, lastcount;
3875     long ucontext = 0, status;
3876     short flags = MAIL$M_NEWMSG;
3877     /* *INDENT-OFF* */
3878     VMSMailItemList
3879       null_list[] = {{0,0,0,0}},
3880       jpi_list[]  = {{sizeof(user) - 1,JPI$_USERNAME,(void *)user,&userlen},
3881 		     {0,0,0,0}},
3882       uilist[]	  = {{0,MAIL$_USER_USERNAME,0,0},
3883 		     {0,0,0,0}},
3884       uolist[]	  = {{sizeof(new),MAIL$_USER_NEW_MESSAGES,&new,0},
3885 		     {sizeof(dir),MAIL$_USER_FULL_DIRECTORY,dir,&dirlen},
3886 		     {0,0,0,0}};
3887     /* *INDENT-ON* */
3888 
3889     extern long mail$user_begin();
3890     extern long mail$user_get_info();
3891     extern long mail$user_end();
3892 
3893     if (failure)
3894 	return;
3895 
3896     if (firsttime) {
3897 	firsttime = FALSE;
3898 	/* Get the username. */
3899 	status = sys$getjpiw(0, 0, 0, jpi_list, 0, 0, 0);
3900 	if (!(status & 1)) {
3901 	    failure = TRUE;
3902 	    return;
3903 	}
3904 	user[userlen] = '\0';
3905 	LYTrimTrailing(user);
3906     }
3907 
3908     /* Minimum report interval is 60 sec. */
3909     time(&now);
3910     if (now - lastcheck < 60)
3911 	return;
3912     lastcheck = now;
3913 
3914     /* Get the current newmail count. */
3915     status = mail$user_begin(&ucontext, null_list, null_list);
3916     if (!(status & 1)) {
3917 	failure = TRUE;
3918 	return;
3919     }
3920     uilist[0].buffer_length = strlen(user);
3921     uilist[0].buffer_address = user;
3922     status = mail$user_get_info(&ucontext, uilist, uolist);
3923     if (!(status & 1)) {
3924 	failure = TRUE;
3925 	return;
3926     }
3927 
3928     /* Should we report anything to the user? */
3929     if (new > 0) {
3930 	if (lastcount == 0)
3931 	    /* Have newmail at startup of Lynx. */
3932 	    HTUserMsg(HAVE_UNREAD_MAIL_MSG);
3933 	else if (new > lastcount)
3934 	    /* Have additional mail since last report. */
3935 	    HTUserMsg(HAVE_NEW_MAIL_MSG);
3936 	lastcount = new;
3937 	return;
3938     }
3939     lastcount = new;
3940 
3941     /* Clear the context */
3942     mail$user_end((long *) &ucontext, null_list, null_list);
3943     return;
3944 }
3945 #else
LYCheckMail(void)3946 void LYCheckMail(void)
3947 {
3948     static BOOL firsttime = TRUE;
3949     static char *mf;
3950     static time_t lastcheck;
3951     static time_t lasttime;
3952     static long lastsize;
3953     time_t now;
3954     struct stat st;
3955 
3956     if (firsttime) {
3957 	mf = LYGetEnv("MAIL");
3958 	firsttime = FALSE;
3959 	time(&lasttime);
3960     }
3961 
3962     if (mf == NULL)
3963 	return;
3964 
3965     time(&now);
3966     if (now - lastcheck < 60)
3967 	return;
3968     lastcheck = now;
3969 
3970     if ((stat(mf, &st) < 0)
3971 	|| !S_ISREG(st.st_mode)) {
3972 	mf = NULL;
3973 	return;
3974     }
3975 
3976     if (st.st_size > 0) {
3977 	if (((lasttime != st.st_mtime) && (st.st_mtime > st.st_atime))
3978 	    || ((lastsize != 0) && (st.st_size > lastsize)))
3979 	    HTUserMsg(HAVE_NEW_MAIL_MSG);
3980 	else if (lastsize == 0)
3981 	    HTUserMsg(HAVE_MAIL_MSG);
3982     }
3983     lastsize = (long) st.st_size;
3984     lasttime = st.st_mtime;
3985     return;
3986 }
3987 #endif /* VMS */
3988 
3989 /*
3990  *  This function ensures that an href will be
3991  *  converted to a fully resolved, absolute URL,
3992  *  with guessing of the host or expansions of
3993  *  lead tildes via LYConvertToURL() if needed,
3994  *  and tweaking/simplifying via HTParse().  It
3995  *  is used for LynxHome, startfile, homepage,
3996  *  and 'g'oto entries, after they have been
3997  *  passed to LYFillLocalFileURL(). - FM
3998  *  Such URLs have no `base' reference to which they
3999  *  could be resolved.  LYLegitimizeHREF could not be used.
4000  */
LYEnsureAbsoluteURL(char ** href,const char * name,int fixit)4001 void LYEnsureAbsoluteURL(char **href,
4002 			 const char *name,
4003 			 int fixit)
4004 {
4005     char *temp = NULL;
4006 
4007     if (isEmpty(*href))
4008 	return;
4009 
4010     /*
4011      * Check whether to fill in localhost.  - FM
4012      */
4013     LYFillLocalFileURL(href, "file://localhost");
4014 
4015     /*
4016      * If it is not a URL then make it one.
4017      */
4018     if (!strcasecomp(*href, STR_NEWS_URL)) {
4019 	StrAllocCat(*href, "*");
4020     } else if (!strcasecomp(*href, STR_SNEWS_URL)) {
4021 	StrAllocCat(*href, "/*");
4022     }
4023 
4024     if (!is_url(*href)) {
4025 	CTRACE((tfp, "%s%s'%s' is not a URL\n",
4026 		NonNull(name), (name ? " " : ""), *href));
4027 	LYConvertToURL(href, fixit);
4028     }
4029 
4030     temp = HTParse(*href, "", PARSE_ALL);
4031     if (non_empty(temp))
4032 	StrAllocCopy(*href, temp);
4033     FREE(temp);
4034 }
4035 
4036 /*
4037  * Rewrite and reallocate a previously allocated string as a file URL if the
4038  * string resolves to a file or directory on the local system, otherwise as an
4039  * http URL.  - FM
4040  */
LYConvertToURL(char ** AllocatedString,int fixit)4041 void LYConvertToURL(char **AllocatedString,
4042 		    int fixit)
4043 {
4044     char *old_string = *AllocatedString;
4045     char *temp = NULL;
4046     char *cp = NULL;
4047 
4048 #ifndef VMS
4049     struct stat st;
4050 #endif /* !VMS */
4051 
4052     if (isEmpty(old_string))
4053 	return;
4054 
4055 #if defined(USE_DOS_DRIVES)
4056     {
4057 	char *cp_url = *AllocatedString;
4058 
4059 	for (; *cp_url != '\0'; cp_url++)
4060 	    if (*cp_url == '\\')
4061 		*cp_url = '/';
4062 	cp_url--;
4063 	if (LYIsDosDrive(*AllocatedString) && *cp_url == ':')
4064 	    LYAddPathSep(AllocatedString);
4065     }
4066 #endif /* USE_DOS_DRIVES */
4067 
4068     *AllocatedString = NULL;	/* so StrAllocCopy doesn't free it */
4069     StrAllocCopy(*AllocatedString, "file://localhost");
4070 
4071     if (*old_string != '/') {
4072 	char *fragment = NULL;
4073 
4074 #if defined(USE_DOS_DRIVES)
4075 	StrAllocCat(*AllocatedString, "/");
4076 #endif /* USE_DOS_DRIVES */
4077 #ifdef VMS
4078 	/*
4079 	 * Not a SHELL pathspec.  Get the full VMS spec and convert it.
4080 	 */
4081 	char *cur_dir = NULL;
4082 	static char url_file[LY_MAXPATH], file_name[LY_MAXPATH], dir_name[LY_MAXPATH];
4083 	unsigned long context = 0;
4084 
4085 	$DESCRIPTOR(url_file_dsc, url_file);
4086 	$DESCRIPTOR(file_name_dsc, file_name);
4087 	if (LYIsTilde(*old_string)) {
4088 	    /*
4089 	     * On VMS, we'll accept '~' on the command line as Home_Dir(), and
4090 	     * assume the rest of the path, if any, has SHELL syntax.
4091 	     */
4092 	    StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
4093 	    if ((cp = strchr(old_string, '/')) != NULL) {
4094 		/*
4095 		 * Append rest of path, if present, skipping "user" if "~user"
4096 		 * was entered, simplifying, and eliminating any residual
4097 		 * relative elements.  - FM
4098 		 */
4099 		StrAllocCopy(temp, cp);
4100 		LYTrimRelFromAbsPath(temp);
4101 		StrAllocCat(*AllocatedString, temp);
4102 		FREE(temp);
4103 	    }
4104 	    goto have_VMS_URL;
4105 	} else {
4106 	    fragment = trimPoundSelector(old_string);
4107 	    LYStrNCpy(url_file, old_string, sizeof(url_file) - 1);
4108 	}
4109 	url_file_dsc.dsc$w_length = (short) strlen(url_file);
4110 	if (1 & lib$find_file(&url_file_dsc, &file_name_dsc, &context,
4111 			      0, 0, 0, 0)) {
4112 	    /*
4113 	     * We found the file.  Convert to a URL pathspec.
4114 	     */
4115 	    if ((cp = strchr(file_name, ';')) != NULL) {
4116 		*cp = '\0';
4117 	    }
4118 	    LYLowerCase(file_name);
4119 	    StrAllocCat(*AllocatedString, HTVMS_wwwName(file_name));
4120 	    if ((cp = strchr(old_string, ';')) != NULL) {
4121 		StrAllocCat(*AllocatedString, cp);
4122 	    }
4123 	    if (fragment != NULL) {
4124 		restorePoundSelector(fragment);
4125 		StrAllocCat(*AllocatedString, fragment);
4126 		fragment = NULL;
4127 	    }
4128 	} else if ((NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) &&
4129 		   0 == chdir(old_string)) {
4130 	    /*
4131 	     * Probably a directory.  Try converting that.
4132 	     */
4133 	    StrAllocCopy(cur_dir, dir_name);
4134 	    restorePoundSelector(fragment);
4135 	    if (NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) {
4136 		/*
4137 		 * Yup, we got it!
4138 		 */
4139 		LYLowerCase(dir_name);
4140 		StrAllocCat(*AllocatedString, dir_name);
4141 		if (fragment != NULL) {
4142 		    StrAllocCat(*AllocatedString, fragment);
4143 		    fragment = NULL;
4144 		}
4145 	    } else {
4146 		/*
4147 		 * Nope.  Assume it's an http URL with the "http://" defaulted,
4148 		 * if we can't rule out a bad VMS path.
4149 		 */
4150 		fragment = NULL;
4151 		if (strchr(old_string, '[') ||
4152 		    ((cp = strchr(old_string, ':')) != NULL &&
4153 		     !isdigit(UCH(cp[1]))) ||
4154 		    !LYExpandHostForURL(&old_string,
4155 					URLDomainPrefixes,
4156 					URLDomainSuffixes)) {
4157 		    /*
4158 		     * Probably a bad VMS path (but can't be sure).  Use
4159 		     * original pathspec for the error message that will
4160 		     * result.
4161 		     */
4162 		    sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
4163 		    CTRACE((tfp,
4164 			    "Can't find '%s'  Will assume it's a bad path.\n",
4165 			    old_string));
4166 		    StrAllocCat(*AllocatedString, url_file);
4167 		} else {
4168 		    /*
4169 		     * Assume a URL is wanted, so guess the scheme with
4170 		     * "http://" as the default.  - FM
4171 		     */
4172 		    if (!LYAddSchemeForURL(&old_string, "http://")) {
4173 			StrAllocCopy(*AllocatedString, "http://");
4174 			StrAllocCat(*AllocatedString, old_string);
4175 		    } else {
4176 			StrAllocCopy(*AllocatedString, old_string);
4177 		    }
4178 		}
4179 	    }
4180 	} else {
4181 	    /*
4182 	     * Nothing found.  Assume it's an http URL with the "http://"
4183 	     * defaulted, if we can't rule out a bad VMS path.
4184 	     */
4185 	    restorePoundSelector(fragment);
4186 	    fragment = NULL;
4187 
4188 	    if (strchr(old_string, '[') ||
4189 		((cp = strchr(old_string, ':')) != NULL &&
4190 		 !isdigit(UCH(cp[1]))) ||
4191 		!LYExpandHostForURL(&old_string,
4192 				    URLDomainPrefixes,
4193 				    URLDomainSuffixes)) {
4194 		/*
4195 		 * Probably a bad VMS path (but can't be sure).  Use original
4196 		 * pathspec for the error message that will result.
4197 		 */
4198 		sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
4199 		CTRACE((tfp, "Can't find '%s'  Will assume it's a bad path.\n",
4200 			old_string));
4201 		StrAllocCat(*AllocatedString, url_file);
4202 	    } else {
4203 		/*
4204 		 * Assume a URL is wanted, so guess the scheme with "http://"
4205 		 * as the default.  - FM
4206 		 */
4207 		if (!LYAddSchemeForURL(&old_string, "http://")) {
4208 		    StrAllocCopy(*AllocatedString, "http://");
4209 		    StrAllocCat(*AllocatedString, old_string);
4210 		} else {
4211 		    StrAllocCopy(*AllocatedString, old_string);
4212 		}
4213 	    }
4214 	}
4215 	lib$find_file_end(&context);
4216 	FREE(cur_dir);
4217       have_VMS_URL:
4218 	CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
4219 #else /* not VMS: */
4220 #if defined(USE_DOS_DRIVES)
4221 #ifdef _WINDOWS
4222 	if (*old_string == '.') {
4223 	    char fullpath[MAX_PATH + 1];
4224 	    char *filepart = NULL;
4225 	    DWORD chk;
4226 
4227 	    chk = GetFullPathNameA(old_string, MAX_PATH + 1,
4228 				   fullpath, &filepart);
4229 	    if (chk != 0) {
4230 		StrAllocCopy(temp, wwwName(fullpath));
4231 		StrAllocCat(*AllocatedString, temp);
4232 		FREE(temp);
4233 		CTRACE((tfp, "Converted '%s' to '%s'\n",
4234 			old_string, *AllocatedString));
4235 	    } else {
4236 		StrAllocCat(*AllocatedString, old_string);
4237 	    }
4238 	}
4239 #else
4240 	if (strlen(old_string) == 1 && *old_string == '.') {
4241 	    /*
4242 	     * They want .
4243 	     */
4244 	    char curdir[LY_MAXPATH];
4245 
4246 	    StrAllocCopy(temp, wwwName(Current_Dir(curdir)));
4247 	    StrAllocCat(*AllocatedString, temp);
4248 	    FREE(temp);
4249 	    CTRACE((tfp, "Converted '%s' to '%s'\n",
4250 		    old_string, *AllocatedString));
4251 	}
4252 #endif
4253 	else
4254 #endif /* USE_DOS_DRIVES */
4255 	if (LYIsTilde(*old_string)) {
4256 	    char *his_home = NULL;
4257 
4258 	    StrAllocCopy(his_home, old_string);
4259 	    LYTildeExpand(&his_home, FALSE);
4260 	    StrAllocCat(*AllocatedString, his_home);
4261 	    FREE(his_home);
4262 
4263 	    CTRACE((tfp, "Converted '%s' to '%s'\n",
4264 		    old_string, *AllocatedString));
4265 	} else {
4266 	    /*
4267 	     * Create a full path to the current default directory.
4268 	     */
4269 	    char curdir[LY_MAXPATH];
4270 	    char *temp2 = NULL;
4271 	    BOOL is_local = FALSE;
4272 
4273 	    Current_Dir(curdir);
4274 	    /*
4275 	     * Concatenate and simplify, trimming any residual relative
4276 	     * elements.  - FM
4277 	     */
4278 #if defined (USE_DOS_DRIVES)
4279 	    if (old_string[1] != ':' && old_string[1] != '|') {
4280 		StrAllocCopy(temp, wwwName(curdir));
4281 		LYAddHtmlSep(&temp);
4282 		LYStrNCpy(curdir, temp, (sizeof(curdir) - 1));
4283 		StrAllocCat(temp, old_string);
4284 	    } else {
4285 		curdir[0] = '\0';
4286 		/* 1998/01/13 (Tue) 12:24:33 */
4287 		if (old_string[1] == '|')
4288 		    old_string[1] = ':';
4289 		StrAllocCopy(temp, old_string);
4290 
4291 		if (strlen(temp) == 2 && LYIsDosDrive(temp))
4292 		    LYAddPathSep(&temp);
4293 	    }
4294 #else
4295 	    StrAllocCopy(temp, curdir);
4296 	    StrAllocCat(temp, "/");
4297 	    StrAllocCat(temp, old_string);
4298 #endif /* USE_DOS_DRIVES */
4299 	    LYTrimRelFromAbsPath(temp);
4300 	    CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
4301 	    if ((stat(temp, &st) > -1) ||
4302 		LYCanReadFile(temp)) {
4303 		/*
4304 		 * It is a subdirectory or file on the local system.
4305 		 */
4306 #if defined (USE_DOS_DRIVES)
4307 		/* Don't want to see DOS local paths like c: escaped  */
4308 		/* especially when we really have file://localhost/   */
4309 		/* at the beginning.  To avoid any confusion we allow */
4310 		/* escaping the path if URL specials % or # present.  */
4311 		if (strchr(temp, '#') == NULL && strchr(temp, '%') == NULL)
4312 		    StrAllocCopy(cp, temp);
4313 		else
4314 		    cp = HTEscape(temp, URL_PATH);
4315 #else
4316 		cp = HTEscape(temp, URL_PATH);
4317 #endif /* USE_DOS_DRIVES */
4318 		StrAllocCat(*AllocatedString, cp);
4319 		FREE(cp);
4320 		CTRACE((tfp, "Converted '%s' to '%s'\n",
4321 			old_string, *AllocatedString));
4322 		is_local = TRUE;
4323 	    } else {
4324 		char *cp2 = NULL;
4325 
4326 		StrAllocCopy(temp2, curdir);
4327 		LYAddPathSep(&temp2);
4328 		StrAllocCopy(cp, old_string);
4329 		fragment = trimPoundSelector(cp);
4330 		HTUnEscape(cp);	/* unescape given path without fragment */
4331 		StrAllocCat(temp2, cp);		/* append to current dir  */
4332 		StrAllocCopy(cp2, temp2);	/* keep a copy in cp2     */
4333 		LYTrimRelFromAbsPath(temp2);
4334 #ifdef WIN_EX			/* 1998/07/31 (Fri) 09:09:03 */
4335 		HTUnEscape(temp2);	/* for LFN */
4336 #endif
4337 
4338 		if (strcmp(temp2, temp) != 0 &&
4339 		    ((stat(temp2, &st) > -1) ||
4340 		     LYCanReadFile(temp2))) {
4341 		    /*
4342 		     * It is a subdirectory or file on the local system with
4343 		     * escaped characters and/or a fragment to be appended to
4344 		     * the URL.  - FM
4345 		     */
4346 
4347 		    FREE(temp);
4348 		    if (strcmp(cp2, temp2) == 0) {
4349 			/*
4350 			 * LYTrimRelFromAbsPath did nothing, use old_string as
4351 			 * given.  - kw
4352 			 */
4353 			temp = HTEscape(curdir, URL_PATH);
4354 			LYAddHtmlSep(&temp);
4355 			StrAllocCat(temp, old_string);
4356 		    } else {
4357 			temp = HTEscape(temp2, URL_PATH);
4358 			if (fragment != NULL) {
4359 			    restorePoundSelector(fragment);
4360 			    StrAllocCat(temp, fragment);
4361 			}
4362 		    }
4363 		    StrAllocCat(*AllocatedString, temp);
4364 		    CTRACE((tfp, "Converted '%s' to '%s'\n",
4365 			    old_string, *AllocatedString));
4366 		    is_local = TRUE;
4367 
4368 		} else if (strchr(curdir, '#') != NULL ||
4369 			   strchr(curdir, '%') != NULL) {
4370 		    /*
4371 		     * If PWD has some unusual characters, construct a filename
4372 		     * in temp where those are escaped.  This is mostly to
4373 		     * prevent this function from returning with some weird URL
4374 		     * if the LYExpandHostForURL tests further down fail.  - kw
4375 		     */
4376 		    FREE(temp);
4377 		    if (strcmp(cp2, temp2) == 0) {
4378 			/*
4379 			 * LYTrimRelFromAbsPath did nothing, use old_string as
4380 			 * given.  - kw
4381 			 */
4382 			temp = HTEscape(curdir, URL_PATH);
4383 			LYAddHtmlSep(&temp);
4384 			StrAllocCat(temp, old_string);
4385 		    } else {
4386 			temp = HTEscape(temp2, URL_PATH);
4387 			if (fragment != NULL) {
4388 			    restorePoundSelector(fragment);
4389 			    StrAllocCat(temp, fragment);
4390 			}
4391 		    }
4392 		}
4393 		FREE(cp);
4394 		FREE(cp2);
4395 	    }
4396 	    if (is_local == FALSE) {
4397 		/*
4398 		 * It's not an accessible subdirectory or file on the local
4399 		 * system, so assume it's a URL request and guess the scheme
4400 		 * with "http://" as the default.
4401 		 */
4402 		CTRACE((tfp, "Can't stat() or fopen() '%s'\n",
4403 			temp2 ? temp2 : temp));
4404 #ifdef WIN_EX			/* 1998/01/13 (Tue) 09:07:37 */
4405 		{
4406 		    const char *p, *q;
4407 		    char buff[LY_MAXPATH + 128];
4408 
4409 		    p = Home_Dir();
4410 		    q = temp2 ? temp2 : temp;
4411 
4412 		    if (strlen(q) == 3 && LYIsDosDrive(q)) {
4413 			sprintf(buff,
4414 				"'%s' not exist, Goto LynxHome '%s'.", q, p);
4415 			_statusline(buff);
4416 			LYSleepAlert();
4417 			FREE(temp);
4418 			StrAllocCat(*AllocatedString, p);
4419 			goto Retry;
4420 		    }
4421 		}
4422 #endif
4423 		if (LYExpandHostForURL(&old_string,
4424 				       URLDomainPrefixes,
4425 				       URLDomainSuffixes)) {
4426 		    if (!LYAddSchemeForURL(&old_string, "http://")) {
4427 			StrAllocCopy(*AllocatedString, "http://");
4428 			StrAllocCat(*AllocatedString, old_string);
4429 		    } else {
4430 			StrAllocCopy(*AllocatedString, old_string);
4431 		    }
4432 		} else if (fixit) {
4433 		    /* RW 1998Mar16  Restore AllocatedString to 'old_string' */
4434 		    StrAllocCopy(*AllocatedString, old_string);
4435 		} else {
4436 		    /* Return file URL for the file that does not exist */
4437 		    StrAllocCat(*AllocatedString, temp);
4438 		}
4439 #ifdef WIN_EX
4440 	      Retry:
4441 #endif
4442 		CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
4443 	    }
4444 	    FREE(temp);
4445 	    FREE(temp2);
4446 	}
4447 #endif /* VMS */
4448     } else {
4449 	/*
4450 	 * Path begins with a slash.  Simplify and use it.
4451 	 */
4452 	if (old_string[1] == '\0') {
4453 	    /*
4454 	     * Request for root.  Respect it on Unix, but on VMS we treat that
4455 	     * as a listing of the login directory.  - FM
4456 	     */
4457 #ifdef VMS
4458 	    StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
4459 #else
4460 	    StrAllocCat(*AllocatedString, "/");
4461 	} else if ((stat(old_string, &st) > -1) ||
4462 		   LYCanReadFile(old_string)) {
4463 	    /*
4464 	     * It is an absolute directory or file on the local system.  - KW
4465 	     */
4466 	    StrAllocCopy(temp, old_string);
4467 	    LYTrimRelFromAbsPath(temp);
4468 	    CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
4469 	    cp = HTEscape(temp, URL_PATH);
4470 	    StrAllocCat(*AllocatedString, cp);
4471 	    FREE(cp);
4472 	    FREE(temp);
4473 	    CTRACE((tfp, "Converted '%s' to '%s'\n",
4474 		    old_string, *AllocatedString));
4475 #endif /* VMS */
4476 	} else if (LYIsTilde(old_string[1])) {
4477 	    /*
4478 	     * Has a Home_Dir() reference.  Handle it as if there weren't a
4479 	     * lead slash.  - FM
4480 	     */
4481 	    StrAllocCat(*AllocatedString, wwwName(Home_Dir()));
4482 	    if ((cp = strchr((old_string + 1), '/')) != NULL) {
4483 		/*
4484 		 * Append rest of path, if present, skipping "user" if "~user"
4485 		 * was entered, simplifying, and eliminating any residual
4486 		 * relative elements.  - FM
4487 		 */
4488 		StrAllocCopy(temp, cp);
4489 		LYTrimRelFromAbsPath(temp);
4490 		StrAllocCat(*AllocatedString, temp);
4491 		FREE(temp);
4492 	    }
4493 	} else {
4494 	    /*
4495 	     * Normal absolute path.  Simplify, trim any residual relative
4496 	     * elements, and append it.  - FM
4497 	     */
4498 	    StrAllocCopy(temp, old_string);
4499 	    LYTrimRelFromAbsPath(temp);
4500 	    StrAllocCat(*AllocatedString, temp);
4501 	    FREE(temp);
4502 	}
4503 	CTRACE((tfp, "Converted '%s' to '%s'\n",
4504 		old_string, *AllocatedString));
4505     }
4506     FREE(old_string);
4507     /* Pause so we can read the messages before invoking curses */
4508     CTRACE_SLEEP(AlertSecs);
4509 }
4510 
4511 #if defined(_WINDOWS)		/* 1998/06/23 (Tue) 16:45:20 */
4512 
win32_check_interrupt(void)4513 int win32_check_interrupt(void)
4514 {
4515     int c;
4516 
4517     if (kbhit()) {
4518 	c = LYgetch();
4519 	/** Keyboard 'Z' or 'z', or Control-G or Control-C **/
4520 	if (LYCharIsINTERRUPT(c) || c == 0x1b) {
4521 	    return TRUE;
4522 	}
4523     }
4524     return FALSE;
4525 }
4526 
4527 #if !defined(__MINGW32__)
sleep(unsigned sec)4528 void sleep(unsigned sec)
4529 {
4530     unsigned int i, j;
4531 
4532     for (j = 0; j < sec; j++) {
4533 	for (i = 0; i < 10; i++) {
4534 	    Sleep(100);
4535 	    if (kbhit()) {
4536 		(void) LYgetch();
4537 		return;
4538 	    }
4539 	}
4540     }
4541 }
4542 #endif /* !__MINGW32__ */
4543 #endif /* _WINDOWS */
4544 
4545 /*
4546  * This function rewrites and reallocates a previously allocated string so that
4547  * the first element is a confirmed Internet host, and returns TRUE, otherwise
4548  * it does not modify the string and returns FALSE.  It first tries the element
4549  * as is, then, if the element does not end with a dot, it adds prefixes from
4550  * the (comma separated) prefix list argument, and, if the element does not
4551  * begin with a dot, suffixes from the (comma separated) suffix list arguments
4552  * (e.g., www.host.com, then www.host,edu, then www.host.net, then
4553  * www.host.org).  The remaining path, if one is present, will be appended to
4554  * the expanded host.  It also takes into account whether a colon is in the
4555  * element or suffix, and includes that and what follows as a port field for
4556  * the expanded host field (e.g, wfbr:8002/dir/lynx should yield
4557  * www.wfbr.edu:8002/dir/lynx).  The calling function should prepend the scheme
4558  * field (e.g., http://), or pass the string to LYAddSchemeForURL(), if this
4559  * function returns TRUE.  - FM
4560  */
LYExpandHostForURL(char ** AllocatedString,char * prefix_list,char * suffix_list)4561 BOOLEAN LYExpandHostForURL(char **AllocatedString,
4562 			   char *prefix_list,
4563 			   char *suffix_list)
4564 {
4565     char *DomainPrefix = NULL;
4566     const char *StartP, *EndP;
4567     char *DomainSuffix = NULL;
4568     const char *StartS, *EndS;
4569     char *Str = NULL, *StrColon = NULL, *MsgStr = NULL;
4570     char *Host = NULL, *HostColon = NULL, *host = NULL;
4571     char *Path = NULL;
4572     char *Fragment = NULL;
4573     BOOLEAN GotHost = FALSE;
4574     BOOLEAN Startup = (BOOL) (helpfilepath == NULL);
4575 
4576 #ifdef INET6
4577     struct addrinfo hints, *res;
4578     int error;
4579     char *begin;
4580     char *end = NULL;
4581 #endif /* INET6 */
4582 
4583     /*
4584      * If it's a NULL or zero-length string, or if it begins with a slash or
4585      * hash, don't continue pointlessly.  - FM
4586      */
4587     if (!(*AllocatedString) || *AllocatedString[0] == '\0' ||
4588 	*AllocatedString[0] == '/' || *AllocatedString[0] == '#') {
4589 	return GotHost;
4590     }
4591 
4592     /*
4593      * If it's a partial or relative path, don't continue pointlessly.  - FM
4594      */
4595     if (!StrNCmp(*AllocatedString, "..", 2) ||
4596 	!StrNCmp(*AllocatedString, "./", 2)) {
4597 	return GotHost;
4598     }
4599 
4600     /*
4601      * Make a clean copy of the string, and trim off the path if one is
4602      * present, but save the information so we can restore the path after
4603      * filling in the Host[:port] field.  - FM
4604      */
4605     StrAllocCopy(Str, *AllocatedString);
4606     if ((Path = strchr(Str, '/')) != NULL) {
4607 	/*
4608 	 * Have a path.  Any fragment should already be included in Path.  - FM
4609 	 */
4610 	*Path = '\0';
4611     } else {
4612 	/*
4613 	 * No path, so check for a fragment and trim that, to be restored after
4614 	 * filling in the Host[:port] field.  - FM
4615 	 */
4616 	Fragment = trimPoundSelector(Str);
4617     }
4618 
4619     /*
4620      * If the potential host string has a colon, assume it begins a port field,
4621      * and trim it off, but save the information so we can restore the port
4622      * field after filling in the host field.  - FM
4623      */
4624     if ((StrColon = strrchr(Str, ':')) != NULL &&
4625 	isdigit(UCH(StrColon[1])) && strchr(StrColon, ']') == NULL) {
4626 	if (StrColon == Str) {
4627 	    goto cleanup;
4628 	}
4629 	*StrColon = '\0';
4630     }
4631 
4632     /*
4633      * Do a DNS test on the potential host field as presently trimmed.  - FM
4634      */
4635     StrAllocCopy(host, Str);
4636     HTUnEscape(host);
4637     if (LYCursesON) {
4638 	StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
4639 	StrAllocCat(MsgStr, host);
4640 	StrAllocCat(MsgStr, FIRST_SEGMENT);
4641 	HTProgress(MsgStr);
4642     } else if (Startup && !dump_output_immediately) {
4643 	fprintf(stdout, "%s '%s'%s\r\n", WWW_FIND_MESSAGE, host, FIRST_SEGMENT);
4644     }
4645 #ifdef INET6
4646     begin = host;
4647     if (host[0] == '[' && ((end = strrchr(host, ']')))) {
4648 	/*
4649 	 * cut '[' and ']' from the IPv6 address, e.g. [::1]
4650 	 */
4651 	begin = host + 1;
4652 	*end = '\0';
4653     }
4654     memset(&hints, 0, sizeof(hints));
4655     hints.ai_family = PF_UNSPEC;
4656     hints.ai_socktype = SOCK_STREAM;
4657     error = getaddrinfo(begin, "80", &hints, &res);
4658     if (end)
4659 	*end = ']';
4660 
4661     if (!error && res)
4662 #else
4663     if (LYGetHostByName(host) != NULL)
4664 #endif /* INET6 */
4665     {
4666 	/*
4667 	 * Clear any residual interrupt.  - FM
4668 	 */
4669 	if (LYCursesON && HTCheckForInterrupt()) {
4670 	    CTRACE((tfp,
4671 		    "LYExpandHostForURL: Ignoring interrupt because '%s' resolved.\n",
4672 		    host));
4673 	}
4674 
4675 	/*
4676 	 * Return success.  - FM
4677 	 */
4678 	GotHost = TRUE;
4679 	goto cleanup;
4680     } else if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
4681 	/*
4682 	 * Give the user chance to interrupt lookup cycles.  - KW & FM
4683 	 */
4684 	CTRACE((tfp,
4685 		"LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
4686 		host));
4687 
4688 	/*
4689 	 * Return failure.  - FM
4690 	 */
4691 	goto cleanup;
4692     }
4693 
4694     /*
4695      * Set the first prefix, making it a zero-length string if the list is NULL
4696      * or if the potential host field ends with a dot.  - FM
4697      */
4698     StartP = ((prefix_list && Str[strlen(Str) - 1] != '.')
4699 	      ? prefix_list
4700 	      : "");
4701     /*
4702      * If we have a prefix, but the allocated string is one of the common host
4703      * prefixes, make our prefix a zero-length string.  - FM
4704      */
4705     if (*StartP && *StartP != '.') {
4706 	if (!strncasecomp(*AllocatedString, "www.", 4) ||
4707 	    !strncasecomp(*AllocatedString, "ftp.", 4) ||
4708 	    !strncasecomp(*AllocatedString, "gopher.", 7) ||
4709 	    !strncasecomp(*AllocatedString, "wais.", 5) ||
4710 	    !strncasecomp(*AllocatedString, "cso.", 4) ||
4711 	    !strncasecomp(*AllocatedString, "ns.", 3) ||
4712 	    !strncasecomp(*AllocatedString, "ph.", 3) ||
4713 	    !strncasecomp(*AllocatedString, "finger.", 7) ||
4714 	    !strncasecomp(*AllocatedString, "news.", 5) ||
4715 	    !strncasecomp(*AllocatedString, "nntp.", 5)) {
4716 	    StartP = "";
4717 	}
4718     }
4719     while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
4720 	StartP++;		/* Skip whitespace and separators */
4721     }
4722     EndP = StartP;
4723     while (*EndP && !WHITE(*EndP) && *EndP != ',') {
4724 	EndP++;			/* Find separator */
4725     }
4726     StrAllocCopy(DomainPrefix, StartP);
4727     DomainPrefix[EndP - StartP] = '\0';
4728 
4729     /*
4730      * Test each prefix with each suffix.  - FM
4731      */
4732     do {
4733 	/*
4734 	 * Set the first suffix, making it a zero-length string if the list is
4735 	 * NULL or if the potential host field begins with a dot.  - FM
4736 	 */
4737 	StartS = ((suffix_list && *Str != '.')
4738 		  ? suffix_list
4739 		  : "");
4740 	while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
4741 	    StartS++;		/* Skip whitespace and separators */
4742 	}
4743 	EndS = StartS;
4744 	while (*EndS && !WHITE(*EndS) && *EndS != ',') {
4745 	    EndS++;		/* Find separator */
4746 	}
4747 	StrAllocCopy(DomainSuffix, StartS);
4748 	DomainSuffix[EndS - StartS] = '\0';
4749 
4750 	/*
4751 	 * Create domain names and do DNS tests.  - FM
4752 	 */
4753 	do {
4754 	    StrAllocCopy(Host, DomainPrefix);
4755 	    StrAllocCat(Host, ((*Str == '.') ? (Str + 1) : Str));
4756 	    if (Host[strlen(Host) - 1] == '.') {
4757 		Host[strlen(Host) - 1] = '\0';
4758 	    }
4759 	    StrAllocCat(Host, DomainSuffix);
4760 	    if ((HostColon = strrchr(Host, ':')) != NULL &&
4761 		isdigit(UCH(HostColon[1]))) {
4762 		*HostColon = '\0';
4763 	    }
4764 	    StrAllocCopy(host, Host);
4765 	    HTUnEscape(host);
4766 	    if (LYCursesON) {
4767 		StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
4768 		StrAllocCat(MsgStr, host);
4769 		StrAllocCat(MsgStr, GUESSING_SEGMENT);
4770 		HTProgress(MsgStr);
4771 	    } else if (Startup && !dump_output_immediately) {
4772 		fprintf(stdout, "%s '%s'%s\n", WWW_FIND_MESSAGE, host, GUESSING_SEGMENT);
4773 	    }
4774 	    GotHost = (BOOL) (LYGetHostByName(host) != NULL);
4775 	    if (HostColon != NULL) {
4776 		*HostColon = ':';
4777 	    }
4778 	    if (GotHost == FALSE) {
4779 		/*
4780 		 * Give the user chance to interrupt lookup cycles.  - KW
4781 		 */
4782 		if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
4783 		    CTRACE((tfp,
4784 			    "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
4785 			    host));
4786 		    goto cleanup;	/* We didn't find a valid name. */
4787 		}
4788 
4789 		/*
4790 		 * Advance to the next suffix, or end of suffix list.  - FM
4791 		 */
4792 		StartS = ((*EndS == '\0') ? EndS : (EndS + 1));
4793 		while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
4794 		    StartS++;	/* Skip whitespace and separators */
4795 		}
4796 		EndS = StartS;
4797 		while (*EndS && !WHITE(*EndS) && *EndS != ',') {
4798 		    EndS++;	/* Find separator */
4799 		}
4800 		LYStrNCpy(DomainSuffix, StartS, (EndS - StartS));
4801 	    }
4802 	} while ((GotHost == FALSE) && (*DomainSuffix != '\0'));
4803 
4804 	if (GotHost == FALSE) {
4805 	    /*
4806 	     * Advance to the next prefix, or end of prefix list.  - FM
4807 	     */
4808 	    StartP = ((*EndP == '\0') ? EndP : (EndP + 1));
4809 	    while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
4810 		StartP++;	/* Skip whitespace and separators */
4811 	    }
4812 	    EndP = StartP;
4813 	    while (*EndP && !WHITE(*EndP) && *EndP != ',') {
4814 		EndP++;		/* Find separator */
4815 	    }
4816 	    LYStrNCpy(DomainPrefix, StartP, (EndP - StartP));
4817 	}
4818     } while ((GotHost == FALSE) && (*DomainPrefix != '\0'));
4819 
4820     /*
4821      * If a test passed, restore the port field if we had one and there is no
4822      * colon in the expanded host, and the path if we had one, and reallocate
4823      * the original string with the expanded Host[:port] field included.  - FM
4824      */
4825     if (GotHost) {
4826 	if (StrColon && strchr(Host, ':') == NULL) {
4827 	    *StrColon = ':';
4828 	    StrAllocCat(Host, StrColon);
4829 	}
4830 	if (Path) {
4831 	    *Path = '/';
4832 	    StrAllocCat(Host, Path);
4833 	} else if (Fragment) {
4834 	    StrAllocCat(Host, "/");
4835 	    restorePoundSelector(Fragment);
4836 	    StrAllocCat(Host, Fragment);
4837 	}
4838 	StrAllocCopy(*AllocatedString, Host);
4839     }
4840 
4841     /*
4842      * Clear any residual interrupt.  - FM
4843      */
4844     if (LYCursesON && HTCheckForInterrupt()) {
4845 	CTRACE((tfp,
4846 		"LYExpandHostForURL: Ignoring interrupt because '%s' %s.\n",
4847 		host,
4848 		(GotHost ? "resolved" : "timed out")));
4849     }
4850 
4851     /*
4852      * Clean up and return the last test result.  - FM
4853      */
4854   cleanup:
4855     FREE(DomainPrefix);
4856     FREE(DomainSuffix);
4857     FREE(Str);
4858     FREE(MsgStr);
4859     FREE(Host);
4860     FREE(host);
4861     return GotHost;
4862 }
4863 
4864 /*
4865  * This function rewrites and reallocates a previously allocated string that
4866  * begins with an Internet host name so that the string begins with its guess
4867  * of the scheme based on the first field of the host name, or the default
4868  * scheme if no guess was made, and returns TRUE, otherwise it does not modify
4869  * the string and returns FALSE.  It also returns FALSE without modifying the
4870  * string if the default_scheme argument was NULL or zero-length and no guess
4871  * was made.  - FM
4872  */
LYAddSchemeForURL(char ** AllocatedString,const char * default_scheme)4873 BOOLEAN LYAddSchemeForURL(char **AllocatedString,
4874 			  const char *default_scheme)
4875 {
4876     char *Str = NULL;
4877     BOOLEAN GotScheme = FALSE;
4878 
4879     /*
4880      * If we were passed a NULL or zero-length string, don't continue
4881      * pointlessly.  - FM
4882      */
4883     if (!(*AllocatedString) || *AllocatedString[0] == '\0') {
4884 	return GotScheme;
4885     }
4886 
4887     /*
4888      * Try to guess the appropriate scheme. - FM
4889      */
4890     if (0 == strncasecomp(*AllocatedString, "www", 3)) {
4891 	/*
4892 	 * This could be either http or https, so check the default and
4893 	 * otherwise use "http".  - FM
4894 	 */
4895 	if (default_scheme != NULL &&
4896 	    NULL != strstr(default_scheme, "http")) {
4897 	    StrAllocCopy(Str, default_scheme);
4898 	} else {
4899 	    StrAllocCopy(Str, "http://");
4900 	}
4901 	GotScheme = TRUE;
4902 
4903     } else if (0 == strncasecomp(*AllocatedString, "ftp", 3)) {
4904 	StrAllocCopy(Str, "ftp://");
4905 	GotScheme = TRUE;
4906 
4907     } else if (0 == strncasecomp(*AllocatedString, "gopher", 6)) {
4908 	StrAllocCopy(Str, "gopher://");
4909 	GotScheme = TRUE;
4910 
4911     } else if (0 == strncasecomp(*AllocatedString, "wais", 4)) {
4912 	StrAllocCopy(Str, "wais://");
4913 	GotScheme = TRUE;
4914 
4915     } else if (0 == strncasecomp(*AllocatedString, "cso", 3) ||
4916 	       0 == strncasecomp(*AllocatedString, "ns.", 3) ||
4917 	       0 == strncasecomp(*AllocatedString, "ph.", 3)) {
4918 	StrAllocCopy(Str, "cso://");
4919 	GotScheme = TRUE;
4920 
4921     } else if (0 == strncasecomp(*AllocatedString, "finger", 6)) {
4922 	StrAllocCopy(Str, "finger://");
4923 	GotScheme = TRUE;
4924 
4925     } else if (0 == strncasecomp(*AllocatedString, "news", 4)) {
4926 	/*
4927 	 * This could be either news, snews, or nntp, so check the default, and
4928 	 * otherwise use news.  - FM
4929 	 */
4930 	if ((default_scheme != NULL) &&
4931 	    (NULL != strstr(default_scheme, "news") ||
4932 	     NULL != strstr(default_scheme, "nntp"))) {
4933 	    StrAllocCopy(Str, default_scheme);
4934 	} else {
4935 	    StrAllocCopy(Str, "news://");
4936 	}
4937 	GotScheme = TRUE;
4938 
4939     } else if (0 == strncasecomp(*AllocatedString, "nntp", 4)) {
4940 	StrAllocCopy(Str, "nntp://");
4941 	GotScheme = TRUE;
4942 
4943     }
4944 
4945     /*
4946      * If we've make a guess, use it.  Otherwise, if we were passed a default
4947      * scheme prefix, use that.  - FM
4948      */
4949     if (GotScheme == TRUE) {
4950 	StrAllocCat(Str, *AllocatedString);
4951 	StrAllocCopy(*AllocatedString, Str);
4952 	FREE(Str);
4953 	return GotScheme;
4954 
4955     } else if (non_empty(default_scheme)) {
4956 	StrAllocCopy(Str, default_scheme);
4957 	GotScheme = TRUE;
4958 	StrAllocCat(Str, *AllocatedString);
4959 	StrAllocCopy(*AllocatedString, Str);
4960 	FREE(Str);
4961 	return GotScheme;
4962     }
4963 
4964     return GotScheme;
4965 }
4966 
4967 /*
4968  * This function expects an absolute Unix or VMS SHELL path spec as an
4969  * allocated string, simplifies it, and trims out any residual relative
4970  * elements.  It also checks whether the path had a terminal slash, and if it
4971  * didn't, makes sure that the simplified path doesn't either.  If it's a
4972  * directory, our convention is to exclude "Up to parent" links when a terminal
4973  * slash is present.  - FM
4974  */
LYTrimRelFromAbsPath(char * path)4975 void LYTrimRelFromAbsPath(char *path)
4976 {
4977     char *cp;
4978     int i;
4979     BOOL TerminalSlash;
4980 
4981     /*
4982      * Make sure we have a pointer to an absolute path.  - FM
4983      */
4984     if (path == NULL || !LYIsPathSep(*path))
4985 	return;
4986 
4987     /*
4988      * Check whether the path has a terminal slash.  - FM
4989      */
4990     TerminalSlash = (BOOL) (LYIsPathSep(path[(strlen(path) - 1)]));
4991 
4992     /*
4993      * Simplify the path and then do any necessary trimming.  - FM
4994      */
4995     HTSimplify(path);
4996     cp = path;
4997     while (cp[1] == '.') {
4998 	if (cp[2] == '\0') {
4999 	    /*
5000 	     * Eliminate trailing dot.  - FM
5001 	     */
5002 	    cp[1] = '\0';
5003 	} else if (LYIsPathSep(cp[2])) {
5004 	    /*
5005 	     * Skip over the "/." of a "/./".  - FM
5006 	     */
5007 	    cp += 2;
5008 	} else if (cp[2] == '.' && cp[3] == '\0') {
5009 	    /*
5010 	     * Eliminate trailing dotdot.  - FM
5011 	     */
5012 	    cp[1] = '\0';
5013 	} else if (cp[2] == '.' && cp[3] == '/') {
5014 	    /*
5015 	     * Skip over the "/.." of a "/../".  - FM
5016 	     */
5017 	    cp += 3;
5018 	} else {
5019 	    /*
5020 	     * Done trimming.  - FM
5021 	     */
5022 	    break;
5023 	}
5024     }
5025 
5026     /*
5027      * Load any shifts into path, and eliminate any terminal slash created by
5028      * HTSimplify() or our walk, but not present originally.  - FM
5029      */
5030     if (cp > path) {
5031 	for (i = 0; cp[i] != '\0'; i++)
5032 	    path[i] = cp[i];
5033 	path[i] = '\0';
5034     }
5035     if (TerminalSlash == FALSE) {
5036 	LYTrimPathSep(path);
5037     }
5038 }
5039 
5040 /*
5041  * Example Client-Side Include interface.
5042  *
5043  * This is called from SGML.c and simply returns markup for reporting the URL
5044  * of the document being loaded if a comment begins with "<!--#lynxCSI".  The
5045  * markup will be included as if it were in the document.  Move this function
5046  * to a separate module for doing this kind of thing seriously, someday.  - FM
5047  */
LYDoCSI(char * url,const char * comment,char ** csi)5048 void LYDoCSI(char *url,
5049 	     const char *comment,
5050 	     char **csi)
5051 {
5052     const char *cp = comment;
5053 
5054     if (cp == NULL)
5055 	return;
5056 
5057     if (StrNCmp(cp, "!--#", 4))
5058 	return;
5059 
5060     cp += 4;
5061     if (!strncasecomp(cp, "lynxCSI", 7)) {
5062 	StrAllocCat(*csi, "\n<p align=\"center\">URL: ");
5063 	StrAllocCat(*csi, url);
5064 	StrAllocCat(*csi, "</p>\n\n");
5065     }
5066 
5067     return;
5068 }
5069 
5070 #ifdef VMS
5071 /*
5072  * Define_VMSLogical -- Fote Macrides 04-Apr-1995
5073  * Define VMS logicals in the process table.
5074  */
Define_VMSLogical(char * LogicalName,char * LogicalValue)5075 void Define_VMSLogical(char *LogicalName,
5076 		       char *LogicalValue)
5077 {
5078     $DESCRIPTOR(lname, "");
5079     $DESCRIPTOR(lvalue, "");
5080     $DESCRIPTOR(ltable, "LNM$PROCESS");
5081 
5082     if (isEmpty(LogicalName))
5083 	return;
5084 
5085     lname.dsc$w_length = strlen(LogicalName);
5086     lname.dsc$a_pointer = LogicalName;
5087 
5088     if (isEmpty(LogicalValue)) {
5089 	lib$delete_logical(&lname, &ltable);
5090 	return;
5091     }
5092 
5093     lvalue.dsc$w_length = strlen(LogicalValue);
5094     lvalue.dsc$a_pointer = LogicalValue;
5095     lib$set_logical(&lname, &lvalue, &ltable, 0, 0);
5096     return;
5097 }
5098 #endif /* VMS */
5099 
5100 #ifdef LY_FIND_LEAKS
LYHomeDir_free(void)5101 static void LYHomeDir_free(void)
5102 {
5103     FREE(HomeDir);
5104 }
5105 #endif /* LY_FIND_LEAKS */
5106 
Current_Dir(char * pathname)5107 char *Current_Dir(char *pathname)
5108 {
5109     char *result;
5110 
5111 #ifdef HAVE_GETCWD
5112     result = getcwd(pathname, (size_t) LY_MAXPATH);
5113 #else
5114     result = getwd(pathname);
5115 #endif /* NO_GETCWD */
5116     if (result == 0)
5117 	strcpy(pathname, ".");
5118     return pathname;
5119 }
5120 
5121 /*
5122  * Verify that the given path refers to an existing directory, returning the
5123  * string if the directory exists.  If not, return null.
5124  */
CheckDir(char * path)5125 static char *CheckDir(char *path)
5126 {
5127     struct stat stat_info;
5128 
5129     if (!LYisAbsPath(path)
5130 	|| (HTStat(path, &stat_info) < 0
5131 	    || !S_ISDIR(stat_info.st_mode))) {
5132 	path = NULL;
5133     }
5134     return path;
5135 }
5136 
5137 /*
5138  * Lookup various possibilities for $HOME, and check that the directory exists.
5139  */
HomeEnv(void)5140 static char *HomeEnv(void)
5141 {
5142     char *result = CheckDir(LYGetEnv("HOME"));
5143 
5144 #if defined (USE_DOS_DRIVES)
5145     if (result == 0) {
5146 	char *head;
5147 	char *leaf;
5148 	static char *temp = NULL;
5149 
5150 	/* Windows 2000 */
5151 	if ((result = LYGetEnv("USERPROFILE")) != 0) {
5152 	    HTSprintf0(&temp, "%s%sMy Documents", result, PATHSEP_STR);
5153 	    result = CheckDir(temp);
5154 	}
5155 	/* NT4 */
5156 	if (result == 0) {
5157 	    if ((head = LYGetEnv("HOMEDRIVE")) != 0) {
5158 		if ((leaf = LYGetEnv("HOMEPATH")) != 0) {
5159 		    HTSprintf0(&temp, "%s%s%s", head, PATHSEP_STR, leaf);
5160 		    result = CheckDir(temp);
5161 		}
5162 	    }
5163 	}
5164 	/* General M$ */
5165 #ifdef USE_PROGRAM_DIR
5166 	if (result == 0)
5167 	    result = CheckDir(program_dir);
5168 #endif
5169 	if (result == 0)
5170 	    result = CheckDir(LYGetEnv("TEMP"));
5171 	if (result == 0)
5172 	    result = CheckDir(LYGetEnv("TMP"));
5173 	if (result == 0) {
5174 	    if ((head = LYGetEnv("SystemDrive")) != 0) {
5175 		HTSprintf0(&temp, "%s%s", head, PATHSEP_STR);
5176 		result = CheckDir(temp);
5177 	    }
5178 	}
5179 	if (result == 0)
5180 	    result = CheckDir("C:" PATHSEP_STR);
5181     }
5182 #endif
5183 
5184     return result;
5185 }
5186 
Home_Dir(void)5187 const char *Home_Dir(void)
5188 {
5189     static const char *homedir = NULL;
5190     char *cp = NULL;
5191 
5192     if (homedir == NULL) {
5193 	if ((cp = HomeEnv()) == NULL) {
5194 #ifdef VMS
5195 	    if ((cp = LYGetEnv("SYS$LOGIN")) == NULL
5196 		&& (cp = LYGetEnv("SYS$SCRATCH")) == NULL) {
5197 		cp = "sys$scratch:";
5198 	    }
5199 	    StrAllocCopy(HomeDir, cp);
5200 #else
5201 #ifdef UNIX
5202 #ifdef HAVE_UTMP
5203 	    /*
5204 	     * One could use getlogin() and getpwnam() here instead.
5205 	     */
5206 	    struct passwd *pw = getpwuid(geteuid());
5207 
5208 	    if (pw && pw->pw_dir) {
5209 		StrAllocCopy(HomeDir, pw->pw_dir);
5210 	    } else
5211 #endif
5212 	    {
5213 		/*
5214 		 * Use /tmp; it should be writable.
5215 		 */
5216 		StrAllocCopy(HomeDir, "/tmp");
5217 	    }
5218 #endif
5219 #endif /* VMS */
5220 	} else {
5221 	    StrAllocCopy(HomeDir, cp);
5222 	}
5223 	homedir = (const char *) HomeDir;
5224 #ifdef LY_FIND_LEAKS
5225 	atexit(LYHomeDir_free);
5226 #endif
5227     }
5228     if (homedir == NULL) {
5229 	printf("%s\n", gettext("Cannot find HOME directory"));
5230 	exit_immediately(EXIT_FAILURE);
5231     }
5232     return homedir;
5233 }
5234 
5235 /*
5236  * Return a pointer to the final leaf of the given pathname, If no pathname
5237  * separators are found, returns the original pathname.  The leaf may be
5238  * empty.
5239  */
LYPathLeaf(char * pathname)5240 char *LYPathLeaf(char *pathname)
5241 {
5242     char *leaf;
5243 
5244 #ifdef UNIX
5245     if ((leaf = strrchr(pathname, '/')) != 0) {
5246 	leaf++;
5247     }
5248 #else
5249 #ifdef VMS
5250     if ((leaf = strrchr(pathname, ']')) == 0)
5251 	leaf = strrchr(pathname, ':');
5252     if (leaf != 0)
5253 	leaf++;
5254 #else
5255     int n;
5256 
5257     for (leaf = 0, n = strlen(pathname) - 1; n >= 0; n--) {
5258 	if (strchr("\\/:", pathname[n]) != 0) {
5259 	    leaf = pathname + n + 1;
5260 	    break;
5261 	}
5262     }
5263 #endif
5264 #endif
5265     return (leaf != 0) ? leaf : pathname;
5266 }
5267 
5268 /*
5269  * This function checks the acceptability of file paths that are intended to be
5270  * off the home directory.  The file path should be passed in fbuffer, together
5271  * with the size of the buffer.  The function simplifies the file path, and if
5272  * it is acceptable, loads it into fbuffer and returns TRUE.  Otherwise, it
5273  * does not modify fbuffer and returns FALSE.  If a subdirectory is present and
5274  * the path does not begin with "./", that is prefixed to make the situation
5275  * clear.  - FM
5276  */
LYPathOffHomeOK(char * fbuffer,size_t fbuffer_size)5277 BOOLEAN LYPathOffHomeOK(char *fbuffer,
5278 			size_t fbuffer_size)
5279 {
5280     char *file = NULL;
5281     char *cp, *cp1;
5282 
5283     /*
5284      * Make sure we have an fbuffer and a string in it.  - FM
5285      */
5286     if (fbuffer_size < 2 || isEmpty(fbuffer)) {
5287 	return (FALSE);
5288     }
5289     StrAllocCopy(file, fbuffer);
5290     cp = file;
5291 
5292     /*
5293      * Check for an inappropriate reference to the home directory, and correct
5294      * it if we can.  - FM
5295      */
5296 #ifdef VMS
5297     if (!strncasecomp(cp, "sys$login", 9)) {
5298 	if (*(cp + 9) == '\0') {
5299 	    /*
5300 	     * Reject "sys$login".  - FM
5301 	     */
5302 	    FREE(file);
5303 	    return (FALSE);
5304 	}
5305 	if (*(cp + 9) == ':') {
5306 	    cp += 10;
5307 	    if (*cp == '\0') {
5308 		/*
5309 		 * Reject "sys$login:".  Otherwise, we have converted
5310 		 * "sys$login:file" to "file", or have left a strange path for
5311 		 * VMS as it was originally.  - FM
5312 		 */
5313 		FREE(file);
5314 		return (FALSE);
5315 	    }
5316 	}
5317     }
5318 #endif /* VMS */
5319     if (LYIsTilde(cp[0])) {
5320 	if (LYIsPathSep(cp[1])) {
5321 	    if (cp[2] != '\0') {
5322 		if (strchr((cp + 2), '/') != NULL) {
5323 		    /*
5324 		     * Convert "~/subdir(s)/file" to "./subdir(s)/file".  - FM
5325 		     */
5326 		    *cp = '.';
5327 		} else {
5328 		    /*
5329 		     * Convert "~/file" to "file".  - FM
5330 		     */
5331 		    cp += 2;
5332 		}
5333 	    } else {
5334 		/*
5335 		 * Reject "~/".  - FM
5336 		 */
5337 		FREE(file);
5338 		return (FALSE);
5339 	    }
5340 	} else if ((*(cp + 1) != '\0') &&
5341 		   (cp1 = strchr((cp + 1), '/')) != NULL) {
5342 	    cp = (cp1 - 1);
5343 	    if (*(cp + 2) != '\0') {
5344 		if (strchr((cp + 2), '/') != NULL) {
5345 		    /*
5346 		     * Convert "~user/subdir(s)/file" to "./subdir(s)/file".
5347 		     * If user is someone else, we covered a spoof.  Otherwise,
5348 		     * we simplified.  - FM
5349 		     */
5350 		    *cp = '.';
5351 		} else {
5352 		    /*
5353 		     * Convert "~user/file" to "file".  - FM
5354 		     */
5355 		    cp += 2;
5356 		}
5357 	    } else {
5358 		/*
5359 		 * Reject "~user/".  - FM
5360 		 */
5361 		FREE(file);
5362 		return (FALSE);
5363 	    }
5364 	} else {
5365 	    /*
5366 	     * Reject "~user".  - FM
5367 	     */
5368 	    FREE(file);
5369 	    return (FALSE);
5370 	}
5371     }
5372 #ifdef VMS
5373     /*
5374      * Check for VMS path specs, and reject if still present.  - FM
5375      */
5376     if (strchr(cp, ':') != NULL || strchr(cp, ']') != NULL) {
5377 	FREE(file);
5378 	return (FALSE);
5379     }
5380 #endif /* VMS */
5381 
5382     /*
5383      * Check for a URL or absolute path, and reject if present.  - FM
5384      */
5385     if (is_url(cp) || LYIsPathSep(*cp)) {
5386 	FREE(file);
5387 	return (FALSE);
5388     }
5389 
5390     /*
5391      * Simplify it.  - FM
5392      */
5393     HTSimplify(cp);
5394 
5395     /*
5396      * Check if it has a pointless "./".  - FM
5397      */
5398     if (!StrNCmp(cp, "./", 2)) {
5399 	if (strchr((cp + 2), '/') == NULL) {
5400 	    cp += 2;
5401 	}
5402     }
5403 
5404     /*
5405      * Check for spoofing.  - FM
5406      */
5407     if (*cp == '\0'
5408 	|| LYIsPathSep(*cp)
5409 	|| LYIsPathSep(cp[(strlen(cp) - 1)])
5410 	|| strstr(cp, "..") != NULL
5411 	|| !strcmp(cp, ".")) {
5412 	FREE(file);
5413 	return (FALSE);
5414     }
5415 
5416     /*
5417      * Load what we have at this point into fbuffer, trimming if too long, and
5418      * claim it's OK.  - FM
5419      */
5420     if (fbuffer_size > 3 && StrNCmp(cp, "./", 2) && strchr(cp, '/')) {
5421 	/*
5422 	 * We have a subdirectory and no lead "./", so prefix it to make the
5423 	 * situation clear.  - FM
5424 	 */
5425 	strcpy(fbuffer, "./");
5426 	if (strlen(cp) > (fbuffer_size - 3))
5427 	    cp[(fbuffer_size - 3)] = '\0';
5428 	strcat(fbuffer, cp);
5429     } else {
5430 	if (strlen(cp) > (fbuffer_size - 1))
5431 	    cp[(fbuffer_size - 1)] = '\0';
5432 	strcpy(fbuffer, cp);
5433     }
5434     FREE(file);
5435     return (TRUE);
5436 }
5437 
5438 /*
5439  * Search for a leading tilde, optionally embedded.  If found, return a pointer
5440  * to the tilde.  If not found, return the original parameter.
5441  */
FindLeadingTilde(char * pathname,int embedded)5442 static char *FindLeadingTilde(char *pathname, int embedded)
5443 {
5444     char *result = pathname;
5445 
5446     if (pathname != NULL) {
5447 	if (embedded) {
5448 	    while (pathname[0] != '\0') {
5449 		if (LYIsPathSep(pathname[0])) {
5450 		    if (LYIsTilde(pathname[1])) {
5451 			++pathname;
5452 			break;
5453 		    }
5454 		}
5455 		++pathname;
5456 	    }
5457 	}
5458 	if (LYIsTilde(*pathname))
5459 	    result = pathname;
5460     }
5461     return result;
5462 }
5463 
5464 /*
5465  * Convert a non-absolute path to one which is off the home directory.  Expand
5466  * tildes as a side-effect.  Return a pointer to the converted result.
5467  */
LYAbsOrHomePath(char ** fname)5468 char *LYAbsOrHomePath(char **fname)
5469 {
5470     if (*fname && !LYisAbsPath(*fname)) {
5471 	if (LYIsTilde((*fname)[0])) {
5472 	    LYTildeExpand(fname, FALSE);
5473 	} else {
5474 	    char temp[LY_MAXPATH];
5475 
5476 	    LYAddPathToHome(temp, sizeof(temp), *fname);
5477 	    StrAllocCopy(*fname, temp);
5478 	}
5479     }
5480     return *fname;
5481 }
5482 
5483 /*
5484  * Expand a "leading" tilde into the user's home directory in WWW format.  If
5485  * "embedded" is true, allow that "leading" tilde to follow a path separator.
5486  */
LYTildeExpand(char ** pathname,int embedded)5487 char *LYTildeExpand(char **pathname,
5488 		    int embedded)
5489 {
5490     char *temp = FindLeadingTilde(*pathname, embedded);
5491 
5492     if (LYIsTilde(temp[0])) {
5493 
5494 	CTRACE((tfp, "LYTildeExpand %s\n", *pathname));
5495 	if (LYIsPathSep(temp[1])) {
5496 	    char *first = NULL;
5497 	    char *second = NULL;
5498 
5499 	    StrAllocCopy(first, *pathname);
5500 	    first[temp - *pathname] = '\0';
5501 
5502 	    StrAllocCopy(second, temp + 2);
5503 
5504 	    StrAllocCopy(*pathname, first);
5505 	    StrAllocCat(*pathname, wwwName(Home_Dir()));
5506 	    LYAddPathSep(pathname);
5507 	    StrAllocCat(*pathname, second);
5508 
5509 	    FREE(first);
5510 	    FREE(second);
5511 	} else if (temp[1] == '\0') {
5512 	    StrAllocCopy(*pathname, wwwName(Home_Dir()));
5513 #ifndef NOUSERS
5514 	} else {
5515 	    char *save;
5516 	    char saved = '\0';
5517 	    struct passwd *pw;
5518 
5519 	    for (save = temp; *save != '\0'; ++save) {
5520 		if (LYIsPathSep(*save)) {
5521 		    saved = *save;
5522 		    *save = '\0';
5523 		    break;
5524 		}
5525 	    }
5526 	    pw = getpwnam(temp + 1);
5527 	    *save = saved;
5528 	    if (pw != 0 && non_empty(pw->pw_dir)) {
5529 		temp = NULL;
5530 		StrAllocCopy(temp, save);
5531 		StrAllocCopy(*pathname, pw->pw_dir);
5532 		StrAllocCat(*pathname, temp);
5533 		FREE(temp);
5534 	    }
5535 #endif
5536 	}
5537 	CTRACE((tfp, "expanded path %s\n", *pathname));
5538     }
5539     return *pathname;
5540 }
5541 
5542 /*
5543  * This function appends fname to the home path and returns the full path and
5544  * filename.  The fname string can be just a filename (e.g.,
5545  * "lynx_bookmarks.html"), or include a subdirectory off the home directory, in
5546  * which case fname should begin with "./" (e.g., ./BM/lynx_bookmarks.html) Use
5547  * LYPathOffHomeOK() to check and/or fix up fname before calling this function.
5548  * On VMS, the resultant full path and filename are converted to VMS syntax.  -
5549  * FM
5550  */
LYAddPathToHome(char * fbuffer,size_t fbuffer_size,const char * fname)5551 void LYAddPathToHome(char *fbuffer,
5552 		     size_t fbuffer_size,
5553 		     const char *fname)
5554 {
5555     char *home = NULL;
5556     const char *file = fname;
5557     int len;
5558 
5559     /*
5560      * Make sure we have a buffer.  - FM
5561      */
5562     if (!fbuffer)
5563 	return;
5564     if (fbuffer_size < 2) {
5565 	fbuffer[0] = '\0';
5566 	return;
5567     }
5568     fbuffer[(fbuffer_size - 1)] = '\0';
5569 
5570     /*
5571      * Make sure we have a file name.  - FM
5572      */
5573     if (!file)
5574 	file = "";
5575 
5576     /*
5577      * Set up home string and length.  - FM
5578      */
5579     StrAllocCopy(home, Home_Dir());
5580 
5581 #ifdef VMS
5582 #define NO_HOMEPATH "Error:"
5583 #else
5584 #define NO_HOMEPATH "/error"
5585 #endif /* VMS */
5586     if (!non_empty(home))
5587 	/*
5588 	 * Home_Dir() has a bug if this ever happens.  - FM
5589 	 */
5590 	StrAllocCopy(home, NO_HOMEPATH);
5591 
5592     len = (int) fbuffer_size - ((int) strlen(home) + 1);
5593     if (len <= 0) {
5594 	/*
5595 	 * Buffer is smaller than or only big enough for the home path.  Load
5596 	 * what fits of the home path and return.  This will fail, but we need
5597 	 * something in the buffer.  - FM
5598 	 */
5599 	LYStrNCpy(fbuffer, home, (fbuffer_size - 1));
5600 	FREE(home);
5601 	return;
5602     }
5603 #ifdef VMS
5604     /*
5605      * Check whether we have a subdirectory path or just a filename.  - FM
5606      */
5607     if (!StrNCmp(file, "./", 2)) {
5608 	/*
5609 	 * We have a subdirectory path.  - FM
5610 	 */
5611 	if (home[strlen(home) - 1] == ']') {
5612 	    /*
5613 	     * We got the home directory, so convert it to SHELL syntax and
5614 	     * append subdirectory path, then convert that to VMS syntax.  - FM
5615 	     */
5616 	    char *temp = NULL;
5617 
5618 	    HTSprintf0(&temp, "%s%s", HTVMS_wwwName(home), (file + 1));
5619 	    sprintf(fbuffer, "%.*s",
5620 		    (fbuffer_size - 1), HTVMS_name("", temp));
5621 	    FREE(temp);
5622 	} else {
5623 	    /*
5624 	     * This will fail, but we need something in the buffer.  - FM
5625 	     */
5626 	    sprintf(fbuffer, "%s%.*s", home, len, file);
5627 	}
5628     } else {
5629 	/*
5630 	 * We have a file in the home directory.  - FM
5631 	 */
5632 	sprintf(fbuffer, "%s%.*s", home, len, file);
5633     }
5634 #else
5635     /*
5636      * Check whether we have a subdirectory path or just a filename.  - FM
5637      */
5638     sprintf(fbuffer, "%s/%.*s", home, len,
5639 	    (StrNCmp(file, "./", 2) ? file : (file + 2)));
5640 #endif /* VMS */
5641     FREE(home);
5642 }
5643 
5644 /*
5645  * Given a filename, concatenate it to the save-space pathname, unless it is
5646  * an absolute pathname.  If there is no save-space defined, use the home
5647  * directory. Return a new string with the result.
5648  */
LYAddPathToSave(char * fname)5649 char *LYAddPathToSave(char *fname)
5650 {
5651     char *result = NULL;
5652 
5653     if (LYisAbsPath(fname)) {
5654 	StrAllocCopy(result, fname);
5655     } else {
5656 	if (lynx_save_space != NULL) {
5657 	    StrAllocCopy(result, lynx_save_space);
5658 	} else {
5659 	    char temp[LY_MAXPATH];
5660 
5661 	    LYAddPathToHome(temp, sizeof(temp), fname);
5662 	    StrAllocCopy(result, temp);
5663 	}
5664     }
5665     return result;
5666 }
5667 
5668 #if !defined(HAVE_PUTENV) && !defined(_WINDOWS)
5669 /*
5670  * No putenv on the NeXT so we use this code instead!
5671  */
5672 
5673 /* Copyright (C) 1991 Free Software Foundation, Inc.
5674 This file is part of the GNU C Library.
5675 
5676 The GNU C Library is free software; you can  redistribute it and/or
5677 modify it under the terms of the GNU Library General  Public License as
5678 published by the Free Software Foundation; either  version 2 of the
5679 License, or (at your option) any later version.
5680 
5681 The GNU C Library is distributed in the hope that it  will be useful,
5682 but WITHOUT ANY WARRANTY; without even the implied  warranty of
5683 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
5684 Library General Public License for more details.
5685 
5686 You should have received a copy of the GNU Library  General Public
5687 License along with the GNU C Library; see the file  COPYING.LIB.  If
5688 not, write to the Free Software Foundation, Inc., 675  Mass Ave,
5689 Cambridge, MA 02139, USA.  */
5690 
5691 #if defined(STDC_HEADERS) || defined(USG)
5692 #include <string.h>
5693 #else /* Not (STDC_HEADERS or USG): */
5694 #include <strings.h>
5695 #endif /* STDC_HEADERS or USG */
5696 
5697 #ifndef NULL
5698 #define NULL 0
5699 #endif /* !NULL */
5700 
5701 extern char **environ;
5702 
5703 /*
5704  * Put STRING, which is of the form "NAME=VALUE", in the environment.
5705  */
putenv(const char * string)5706 int putenv(const char *string)
5707 {
5708     char *name_end = strchr(string, '=');
5709     register size_t size;
5710     register char **ep;
5711 
5712     if (name_end == NULL) {
5713 	/* Remove the variable from the environment.  */
5714 	size = strlen(string);
5715 	for (ep = environ; *ep != NULL; ++ep)
5716 	    if (!StrNCmp(*ep, string, size) && (*ep)[size] == '=') {
5717 		while (ep[1] != NULL) {
5718 		    ep[0] = ep[1];
5719 		    ++ep;
5720 		}
5721 		*ep = NULL;
5722 		return 0;
5723 	    }
5724     }
5725 
5726     size = 0;
5727     for (ep = environ; *ep != NULL; ++ep)
5728 	if (!StrNCmp(*ep, string, name_end - string) &&
5729 	    (*ep)[name_end - string] == '=')
5730 	    break;
5731 	else
5732 	    ++size;
5733 
5734     if (*ep == NULL) {
5735 	static char **last_environ = NULL;
5736 	char **new_environ = (char **) malloc((size + 2) * sizeof(char *));
5737 
5738 	if (new_environ == NULL)
5739 	    return -1;
5740 	(void) memcpy((char *) new_environ, (char *) environ, size * sizeof(char *));
5741 
5742 	new_environ[size] = (char *) string;
5743 	new_environ[size + 1] = NULL;
5744 	if (last_environ != NULL)
5745 	    FREE(last_environ);
5746 	last_environ = new_environ;
5747 	environ = new_environ;
5748     } else
5749 	*ep = (char *) string;
5750 
5751     return 0;
5752 }
5753 #endif /* !HAVE_PUTENV */
5754 
5755 #ifdef NEED_REMOVE
remove(char * name)5756 int remove(char *name)
5757 {
5758     return unlink(name);
5759 }
5760 #endif
5761 
5762 #if defined(MULTI_USER_UNIX)
5763 
5764 #if defined(HAVE_LSTAT) && defined(S_IFLNK)
5765 /*
5766  * If IsOurFile() is checking a symbolic link, ensure that the target
5767  * points to the user's file as well.
5768  */
IsOurSymlink(const char * name)5769 static BOOL IsOurSymlink(const char *name)
5770 {
5771     BOOL result = FALSE;
5772     size_t size = LY_MAXPATH;
5773     size_t used;
5774     char *buffer = typeMallocn(char, (unsigned) size);
5775     char *check;
5776 
5777     if (buffer != 0) {
5778 	while ((used = (size_t) readlink(name, buffer, (size - 1))) == size - 1) {
5779 	    check = typeRealloc(char, buffer, (unsigned) (size *= 2));
5780 
5781 	    if (check == 0)
5782 		break;
5783 	    buffer = check;
5784 	}
5785 	if (buffer != 0) {
5786 	    if ((int) used > 0) {
5787 		buffer[used] = '\0';
5788 	    } else {
5789 		FREE(buffer);
5790 	    }
5791 	}
5792     }
5793     if (buffer != 0) {
5794 	if (!LYisAbsPath(buffer)) {
5795 	    char *cutoff = LYLastPathSep(name);
5796 	    char *clone = NULL;
5797 
5798 	    if (cutoff != 0) {
5799 		HTSprintf0(&clone, "%.*s%s%s",
5800 			   (int) (cutoff - name),
5801 			   name, PATHSEP_STR, buffer);
5802 		FREE(buffer);
5803 		buffer = clone;
5804 	    }
5805 	}
5806 	CTRACE2(TRACE_CFG, (tfp, "IsOurSymlink(%s -> %s)\n", name, buffer));
5807 	result = IsOurFile(buffer);
5808 	FREE(buffer);
5809     }
5810     return result;
5811 }
5812 #endif
5813 
5814 /*
5815  * Verify if this is really a file, not accessed by a link, except for the
5816  * special case of its directory being pointed to by a link from a directory
5817  * owned by root and not writable by other users.
5818  */
IsOurFile(const char * name)5819 BOOL IsOurFile(const char *name)
5820 {
5821     BOOL result = FALSE;
5822     struct stat data;
5823 
5824     if (!LYIsTilde(name[0])
5825 	&& lstat(name, &data) == 0
5826 	&& ((S_ISREG(data.st_mode)
5827 	     && (data.st_mode & (S_IWOTH | S_IWGRP)) == 0
5828 	     && data.st_nlink == 1
5829 	     && data.st_uid == getuid())
5830 #if defined(HAVE_LSTAT) && defined(S_IFLNK)
5831 	    || (S_ISLNK(data.st_mode) && IsOurSymlink(name))
5832 #endif
5833 	)) {
5834 	int linked = FALSE;
5835 
5836 	/*
5837 	 * ( If this is not a single-user system, the other user is presumed by
5838 	 * some people busy trying to use a symlink attack on our files ;-)
5839 	 */
5840 #if defined(HAVE_LSTAT)
5841 	char *path = 0;
5842 	char *leaf;
5843 
5844 	StrAllocCopy(path, name);
5845 	do {
5846 	    if ((leaf = LYPathLeaf(path)) != path)
5847 		*--leaf = '\0';	/* write a null on the '/' */
5848 	    if (lstat(*path ? path : "/", &data) != 0) {
5849 		break;
5850 	    }
5851 	    /*
5852 	     * If we find a symbolic link, it has to be in a directory that's
5853 	     * protected.  Otherwise someone could have switched it to point
5854 	     * to one of the real user's files.
5855 	     */
5856 	    if (S_ISLNK(data.st_mode)) {
5857 		linked = TRUE;	/* could be link-to-link; doesn't matter */
5858 	    } else if (S_ISDIR(data.st_mode)) {
5859 		if (linked) {
5860 		    linked = FALSE;
5861 		    /*
5862 		     * We assume that a properly-configured system has the
5863 		     * unwritable directories owned by root.  This is not
5864 		     * necessarily so (bin, news, etc., may), but the only
5865 		     * uid we can count on is 0.  It would be nice to add a
5866 		     * check for the gid also, but that wouldn't be
5867 		     * portable.
5868 		     */
5869 		    if (data.st_uid != 0
5870 			|| (data.st_mode & S_IWOTH) != 0) {
5871 			linked = TRUE;	/* force an error-return */
5872 			break;
5873 		    }
5874 		}
5875 	    } else if (linked) {
5876 		break;
5877 	    }
5878 	} while (leaf != path);
5879 	FREE(path);
5880 #endif
5881 	result = (BOOLEAN) !linked;
5882     }
5883     CTRACE2(TRACE_CFG, (tfp, "IsOurFile(%s) %d\n", name, result));
5884     return result;
5885 }
5886 
5887 /*
5888  * Open a file that we don't want other users to see.
5889  */
OpenHiddenFile(const char * name,const char * mode)5890 static FILE *OpenHiddenFile(const char *name, const char *mode)
5891 {
5892     FILE *fp = 0;
5893     struct stat data;
5894     BOOLEAN binary = (BOOLEAN) (strchr(mode, 'b') != 0);
5895 
5896 #if defined(O_CREAT) && defined(O_EXCL)		/* we have fcntl.h or kindred? */
5897     /*
5898      * This is the preferred method for creating new files, since it ensures
5899      * that no one has an existing file or link that they happen to own.
5900      */
5901     if (*mode == 'w') {
5902 	int fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);
5903 
5904 	if (fd < 0
5905 	    && errno == EEXIST
5906 	    && IsOurFile(name)) {
5907 	    if (remove(name) == 0) {
5908 		/* FIXME: there's a race at this point if directory is open */
5909 		fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);
5910 	    }
5911 	}
5912 	if (fd >= 0) {
5913 #if defined(O_BINARY) && defined(__CYGWIN__)
5914 	    if (binary)
5915 		setmode(fd, O_BINARY);
5916 #endif
5917 	    fp = fdopen(fd, mode);
5918 	}
5919     } else
5920 #endif
5921     if (*mode == 'a') {
5922 	if (IsOurFile(name)
5923 	    && chmod(name, HIDE_CHMOD) == 0)
5924 	    fp = fopen(name, mode);
5925 	else if (lstat(name, &data) != 0)
5926 	    fp = OpenHiddenFile(name, binary ? BIN_W : TXT_W);
5927 	/*
5928 	 * This is less stringent, but reasonably portable.  For new files, the
5929 	 * umask will suffice; however if the file already exists we'll change
5930 	 * permissions first, before opening it.  If the chmod fails because of
5931 	 * some reason other than a non-existent file, there's no point in trying
5932 	 * to open it.
5933 	 *
5934 	 * This won't work properly if the user is root, since the chmod succeeds.
5935 	 */
5936     } else if (*mode != 'a') {
5937 	mode_t save = umask(HIDE_UMASK);
5938 
5939 	if (chmod(name, HIDE_CHMOD) == 0 || errno == ENOENT)
5940 	    fp = fopen(name, mode);
5941 	(void) umask(save);
5942     }
5943     return fp;
5944 }
5945 #else
5946 #define OpenHiddenFile(name, mode) fopen(name, mode)
5947 #endif /* MULTI_USER_UNIX */
5948 
LYNewBinFile(const char * name)5949 FILE *LYNewBinFile(const char *name)
5950 {
5951 #ifdef VMS
5952     FILE *fp = fopen(name, BIN_W, "mbc=32");
5953 
5954     (void) chmod(name, HIDE_CHMOD);
5955 #else
5956     FILE *fp = OpenHiddenFile(name, BIN_W);
5957 #endif
5958     return fp;
5959 }
5960 
LYNewTxtFile(const char * name)5961 FILE *LYNewTxtFile(const char *name)
5962 {
5963     FILE *fp;
5964 
5965 #ifdef VMS
5966     fp = fopen(name, TXT_W, "shr=get");
5967     (void) chmod(name, HIDE_CHMOD);
5968 #else
5969     SetDefaultMode(O_TEXT);
5970 
5971     fp = OpenHiddenFile(name, TXT_W);
5972 
5973     SetDefaultMode(O_BINARY);
5974 #endif
5975 
5976     return fp;
5977 }
5978 
LYAppendToTxtFile(const char * name)5979 FILE *LYAppendToTxtFile(const char *name)
5980 {
5981     FILE *fp;
5982 
5983 #ifdef VMS
5984     fp = fopen(name, TXT_A, "shr=get");
5985     (void) chmod(name, HIDE_CHMOD);
5986 #else
5987     SetDefaultMode(O_TEXT);
5988 
5989     fp = OpenHiddenFile(name, TXT_A);
5990 
5991     SetDefaultMode(O_BINARY);
5992 #endif
5993     return fp;
5994 }
5995 
5996 #if defined(MULTI_USER_UNIX)
5997 /*
5998  * Restore normal permissions to a copy of a file that we have created with
5999  * temp file restricted permissions.  The normal umask should apply for user
6000  * files.  - kw
6001  */
LYRelaxFilePermissions(const char * name)6002 void LYRelaxFilePermissions(const char *name)
6003 {
6004     mode_t mode;
6005     struct stat stat_buf;
6006 
6007     if (stat(name, &stat_buf) == 0 &&
6008 	S_ISREG(stat_buf.st_mode) &&
6009 	(mode = (stat_buf.st_mode & 0777)) == HIDE_CHMOD) {
6010 	/*
6011 	 * It looks plausible that this is a file we created with temp file
6012 	 * paranoid permissions (and the umask wasn't even more restrictive
6013 	 * when it was copied).  - kw
6014 	 */
6015 	mode_t save = umask(HIDE_UMASK);
6016 
6017 	mode = ((mode & 0700) | 0066) & ~save;
6018 	(void) umask(save);
6019 	(void) chmod(name, mode);
6020     }
6021 }
6022 #endif
6023 
6024 /*
6025  * Check if the given anchor has an associated file-cache.
6026  */
LYCachedTemp(char * target,char ** cached)6027 BOOLEAN LYCachedTemp(char *target,
6028 		     char **cached)
6029 {
6030     BOOLEAN result = FALSE;
6031 
6032     if (*cached) {
6033 	LYStrNCpy(target, *cached, LY_MAXPATH);
6034 	FREE(*cached);
6035 	if (LYCanReadFile(target)) {
6036 	    if (remove(target) != 0) {
6037 		CTRACE((tfp, "cannot remove %s\n", target));
6038 	    }
6039 	}
6040 	result = TRUE;
6041     }
6042     return result;
6043 }
6044 
6045 #ifndef HAVE_MKDTEMP
6046 #define mkdtemp(path) ((mktemp(path) != 0) && (mkdir(path, 0700) == 0))
6047 #endif
6048 
6049 /*
6050  * Open a temp-file, ensuring that it is unique, and not readable by other
6051  * users.
6052  *
6053  * The mode can be one of: "w", "a", "wb".
6054  */
LYOpenTemp(char * result,const char * suffix,const char * mode)6055 FILE *LYOpenTemp(char *result,
6056 		 const char *suffix,
6057 		 const char *mode)
6058 {
6059     FILE *fp = 0;
6060     BOOL txt = TRUE;
6061     char wrt = 'r';
6062     LY_TEMP *p;
6063 
6064     CTRACE((tfp, "LYOpenTemp(,%s,%s)\n", suffix, mode));
6065     if (result == 0)
6066 	return 0;
6067 
6068     while (*mode != '\0') {
6069 	switch (*mode++) {
6070 	case 'w':
6071 	    wrt = 'w';
6072 	    break;
6073 	case 'a':
6074 	    wrt = 'a';
6075 	    break;
6076 	case 'b':
6077 	    txt = FALSE;
6078 	    break;
6079 	default:
6080 	    CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
6081 	    return 0;
6082 	}
6083     }
6084 
6085     /*
6086      * Verify if the given space looks secure enough.  Otherwise, make a
6087      * secure subdirectory of that.
6088      */
6089 #if defined(MULTI_USER_UNIX) && (defined(HAVE_MKTEMP) || defined(HAVE_MKDTEMP))
6090     if (lynx_temp_subspace == 0) {
6091 	BOOL make_it = FALSE;
6092 	struct stat sb;
6093 
6094 	if (lstat(lynx_temp_space, &sb) == 0
6095 	    && S_ISDIR(sb.st_mode)) {
6096 	    if (sb.st_uid != getuid()
6097 		|| (sb.st_mode & (S_IWOTH | S_IWGRP)) != 0) {
6098 		make_it = TRUE;
6099 		CTRACE((tfp,
6100 			"lynx_temp_space is not our directory %s owner %d mode %03o\n",
6101 			lynx_temp_space, (int) sb.st_uid, (int) sb.st_mode & 0777));
6102 	    }
6103 	} else {
6104 	    make_it = TRUE;
6105 	    CTRACE((tfp, "lynx_temp_space is not a directory %s\n", lynx_temp_space));
6106 	}
6107 	if (make_it) {
6108 	    mode_t old_mask = umask(HIDE_UMASK);
6109 
6110 	    StrAllocCat(lynx_temp_space, "lynxXXXXXXXXXX");
6111 	    if (mkdtemp(lynx_temp_space) == 0) {
6112 		printf("%s: %s\n", lynx_temp_space, LYStrerror(errno));
6113 		exit_immediately(EXIT_FAILURE);
6114 	    }
6115 	    (void) umask(old_mask);
6116 	    lynx_temp_subspace = 1;
6117 	    StrAllocCat(lynx_temp_space, "/");
6118 	    CTRACE((tfp, "made subdirectory %s\n", lynx_temp_space));
6119 	} else {
6120 	    lynx_temp_subspace = -1;
6121 	}
6122     }
6123 #endif
6124 
6125     do {
6126 	if (!fmt_tempname(result, lynx_temp_space, suffix))
6127 	    return 0;
6128 	if (txt) {
6129 	    switch (wrt) {
6130 	    case 'w':
6131 		fp = LYNewTxtFile(result);
6132 		break;
6133 	    case 'a':
6134 		fp = LYAppendToTxtFile(result);
6135 		break;
6136 	    }
6137 	} else {
6138 	    fp = LYNewBinFile(result);
6139 	}
6140 	/*
6141 	 * If we get a failure to make a temporary file, don't bother to try a
6142 	 * different name unless the failure was because the file already
6143 	 * exists.
6144 	 */
6145 #ifdef EEXIST			/* FIXME (need a better test) in fcntl.h or unistd.h */
6146 	if ((fp == 0) && (errno != EEXIST)) {
6147 	    CTRACE((tfp, "... LYOpenTemp(%s) failed: %s\n",
6148 		    result, LYStrerror(errno)));
6149 	    return 0;
6150 	}
6151 #endif
6152     } while (fp == 0);
6153 
6154     if ((p = typecalloc(LY_TEMP)) != 0) {
6155 	p->next = ly_temp;
6156 	StrAllocCopy((p->name), result);
6157 	p->file = fp;
6158 	p->outs = (BOOLEAN) (wrt != 'r');
6159 	ly_temp = p;
6160     } else {
6161 	outofmem(__FILE__, "LYOpenTemp");
6162     }
6163 
6164     CTRACE((tfp, "... LYOpenTemp(%s)\n", result));
6165     return fp;
6166 }
6167 
6168 /*
6169  * Reopen a temporary file
6170  */
LYReopenTemp(char * name)6171 FILE *LYReopenTemp(char *name)
6172 {
6173     LY_TEMP *p;
6174     FILE *fp = 0;
6175 
6176     LYCloseTemp(name);
6177     if ((p = FindTempfileByName(name)) != 0) {
6178 	fp = p->file = LYAppendToTxtFile(name);
6179     }
6180     return fp;
6181 }
6182 
6183 /*
6184  * Open a temp-file for writing, possibly re-using a previously used
6185  * name and file.
6186  * If a non-empty fname is given, it is reused if it indicates a file
6187  * previously registered as a temp file and, in case the file still
6188  * exists, if it looks like we can write to it safely.  Otherwise a
6189  * new temp file (with new name) will be generated and returned in fname.
6190  *
6191  * File permissions are set so that the file is not readable by unprivileged
6192  * other users.
6193  *
6194  * Suffix is only used if fname is not being reused.
6195  * The mode should be "w", others are possible (they may be passed on)
6196  * but probably don't make sense. - kw
6197  */
LYOpenTempRewrite(char * fname,const char * suffix,const char * mode)6198 FILE *LYOpenTempRewrite(char *fname,
6199 			const char *suffix,
6200 			const char *mode)
6201 {
6202     FILE *fp = 0;
6203     BOOL txt = TRUE;
6204     char wrt = 'r';
6205     BOOL registered = NO;
6206     BOOL writable_exists = NO;
6207     BOOL is_ours = NO;
6208     BOOL still_open = NO;
6209     LY_TEMP *p;
6210     struct stat stat_buf;
6211 
6212     CTRACE((tfp, "LYOpenTempRewrite(%s,%s,%s)\n", fname, suffix, mode));
6213     if (*fname == '\0')		/* first time, no filename yet */
6214 	return (LYOpenTemp(fname, suffix, mode));
6215 
6216     if ((p = FindTempfileByName(fname)) != 0) {
6217 	registered = YES;
6218 	if (p->file != 0)
6219 	    still_open = YES;
6220 	CTRACE((tfp, "...used before%s\n", still_open ? ", still open!" : "."));
6221     }
6222 
6223     if (registered) {
6224 #ifndef NO_GROUPS
6225 	writable_exists = HTEditable(fname);	/* existing, can write */
6226 #define CTRACE_EXISTS "exists and is writable, "
6227 #else
6228 	writable_exists = (BOOL) (stat(fname, &stat_buf) == 0);		/* existing, assume can write */
6229 #define CTRACE_EXISTS "exists, "
6230 #endif
6231 
6232 	if (writable_exists) {
6233 	    is_ours = IsOurFile(fname);
6234 	}
6235 	CTRACE((tfp, "...%s%s\n",
6236 		writable_exists ? CTRACE_EXISTS : "",
6237 		is_ours ? "is our file." : "is NOT our file."));
6238     }
6239 
6240     /*
6241      * Note that in cases where LYOpenTemp is called as fallback below, we
6242      * don't call LYRemoveTemp first.  That may be appropriate in some cases,
6243      * but not trying to remove a weird existing file seems safer and could
6244      * help diagnose an unusual situation.  (They may be removed anyway later.)
6245      */
6246     if (still_open) {
6247 	/*
6248 	 * This should probably not happen.  Make a new one.
6249 	 */
6250 	return (LYOpenTemp(fname, suffix, mode));
6251     } else if (!registered) {
6252 	/*
6253 	 * Not registered.  It should have been registered at one point though,
6254 	 * otherwise we wouldn't be called like this.
6255 	 */
6256 	return (LYOpenTemp(fname, suffix, mode));
6257     } else if (writable_exists && !is_ours) {
6258 	/*
6259 	 * File exists, writable if we checked, but something is wrong with it.
6260 	 */
6261 	return (LYOpenTemp(fname, suffix, mode));
6262 #ifndef NO_GROUPS
6263     } else if (!is_ours && (lstat(fname, &stat_buf) == 0)) {
6264 	/*
6265 	 * Exists but not writable, and something is wrong with it.
6266 	 */
6267 	return (LYOpenTemp(fname, suffix, mode));
6268 #endif
6269     }
6270 
6271     while (*mode != '\0') {
6272 	switch (*mode++) {
6273 	case 'w':
6274 	    wrt = 'w';
6275 	    break;
6276 	case 'a':
6277 	    wrt = 'a';
6278 	    break;
6279 	case 'b':
6280 	    txt = FALSE;
6281 	    break;
6282 	default:
6283 	    CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
6284 	    return fp;
6285 	}
6286     }
6287 
6288     if (is_ours) {
6289 	/*
6290 	 * Yes, it exists, is writable if we checked, and everything looks ok
6291 	 * so far.  This should be the most regular case.  - kw
6292 	 */
6293 #ifdef HAVE_TRUNCATE
6294 	if (txt == TRUE) {	/* limitation of LYReopenTemp.  shrug */
6295 	    /*
6296 	     * We truncate and then append, this avoids having a small window
6297 	     * in which the file doesn't exist.  - kw
6298 	     */
6299 	    if (truncate(fname, (off_t) 0) != 0) {
6300 		CTRACE((tfp, "... truncate(%s,0) failed: %s\n",
6301 			fname, LYStrerror(errno)));
6302 		return (LYOpenTemp(fname, suffix, mode));
6303 	    } else {
6304 		return (LYReopenTemp(fname));
6305 	    }
6306 	}
6307 #endif
6308 	remove(fname);
6309 
6310     }
6311 
6312     /* We come here in two cases:  either the file existed and was ours and we
6313      * just got rid of it.  Or the file did and does not exist, but is
6314      * registered as a temp file.  It must have been removed by some means
6315      * other than LYRemoveTemp.  In both cases, reuse the name!  - kw
6316      */
6317 
6318     if (txt) {
6319 	switch (wrt) {
6320 	case 'w':
6321 	    fp = LYNewTxtFile(fname);
6322 	    break;
6323 	case 'a':
6324 	    fp = LYAppendToTxtFile(fname);
6325 	    break;
6326 	}
6327     } else {
6328 	fp = LYNewBinFile(fname);
6329     }
6330     p->file = fp;
6331 
6332     CTRACE((tfp, "... LYOpenTempRewrite(%s), %s\n", fname,
6333 	    (fp) ? "ok" : "failed"));
6334     /*
6335      * We could fall back to trying LYOpenTemp() here in case of failure.
6336      * After all the checks already done above a filure here should be pretty
6337      * unusual though, so maybe it's better to let the user notice that
6338      * something went wrong, and not try to fix it up.  - kw
6339      */
6340     return fp;
6341 }
6342 
6343 /*
6344  * Special case of LYOpenTemp, used for manipulating bookmark file, i.e., with
6345  * renaming.
6346  */
LYOpenScratch(char * result,const char * prefix)6347 FILE *LYOpenScratch(char *result,
6348 		    const char *prefix)
6349 {
6350     FILE *fp;
6351     LY_TEMP *p;
6352 
6353     if (!fmt_tempname(result, prefix, HTML_SUFFIX))
6354 	return 0;
6355 
6356     if ((fp = LYNewTxtFile(result)) != 0) {
6357 	if ((p = typecalloc(LY_TEMP)) != 0) {
6358 	    p->next = ly_temp;
6359 	    StrAllocCopy((p->name), result);
6360 	    p->file = fp;
6361 	    ly_temp = p;
6362 	} else {
6363 	    outofmem(__FILE__, "LYOpenScratch");
6364 	}
6365     }
6366     CTRACE((tfp, "LYOpenScratch(%s)\n", result));
6367     return fp;
6368 }
6369 
LY_close_temp(LY_TEMP * p)6370 static void LY_close_temp(LY_TEMP * p)
6371 {
6372     if (p->file != 0) {
6373 	if (p->outs) {
6374 	    LYCloseOutput(p->file);
6375 	} else {
6376 	    LYCloseInput(p->file);
6377 	}
6378 	p->file = 0;
6379     }
6380 }
6381 
6382 /*
6383  * Close a temp-file, given its name
6384  */
LYCloseTemp(char * name)6385 void LYCloseTemp(char *name)
6386 {
6387     LY_TEMP *p;
6388 
6389     CTRACE((tfp, "LYCloseTemp(%s)\n", name));
6390     if ((p = FindTempfileByName(name)) != 0) {
6391 	CTRACE((tfp, "...LYCloseTemp(%s)%s\n", name,
6392 		(p->file != 0) ? ", closed" : ""));
6393 	LY_close_temp(p);
6394     }
6395 }
6396 
6397 /*
6398  * Close a temp-file, given its file-pointer
6399  */
LYCloseTempFP(FILE * fp)6400 void LYCloseTempFP(FILE *fp)
6401 {
6402     LY_TEMP *p;
6403 
6404     CTRACE((tfp, "LYCloseTempFP\n"));
6405     if ((p = FindTempfileByFP(fp)) != 0) {
6406 	LY_close_temp(p);
6407 	CTRACE((tfp, "...LYCloseTempFP(%s)\n", p->name));
6408     }
6409 }
6410 
6411 /*
6412  * Close a temp-file, removing it.
6413  */
LYRemoveTemp(char * name)6414 int LYRemoveTemp(char *name)
6415 {
6416     LY_TEMP *p, *q;
6417     int code = -1;
6418 
6419     if (non_empty(name)) {
6420 	CTRACE((tfp, "LYRemoveTemp(%s)\n", name));
6421 	for (p = ly_temp, q = 0; p != 0; q = p, p = p->next) {
6422 	    if (!strcmp(name, p->name)) {
6423 		if (q != 0) {
6424 		    q->next = p->next;
6425 		} else {
6426 		    ly_temp = p->next;
6427 		}
6428 		LY_close_temp(p);
6429 		code = HTSYS_remove(name);
6430 		CTRACE((tfp, "...LYRemoveTemp done(%d)%s\n", code,
6431 			(p->file != 0) ? ", closed" : ""));
6432 		CTRACE_FLUSH(tfp);
6433 		FREE(p->name);
6434 		FREE(p);
6435 		break;
6436 	    }
6437 	}
6438     }
6439     return code;
6440 }
6441 
6442 /*
6443  * Remove all of the temp-files.  Note that this assumes that they are closed,
6444  * since some systems will not allow us to remove a file which is open.
6445  */
LYCleanupTemp(void)6446 void LYCleanupTemp(void)
6447 {
6448     while (ly_temp != 0) {
6449 	(void) LYRemoveTemp(ly_temp->name);
6450     }
6451 #if defined(MULTI_USER_UNIX)
6452     if (lynx_temp_subspace > 0) {
6453 	char result[LY_MAXPATH];
6454 
6455 	LYStrNCpy(result, lynx_temp_space, sizeof(result) - 1);
6456 	LYTrimPathSep(result);
6457 	CTRACE((tfp, "LYCleanupTemp removing %s\n", result));
6458 	rmdir(result);
6459 	lynx_temp_subspace = -1;
6460     }
6461 #endif
6462 }
6463 
6464 /*
6465  * We renamed a temporary file.  Keep track so we can remove it on exit.
6466  */
LYRenamedTemp(char * oldname,char * newname)6467 void LYRenamedTemp(char *oldname,
6468 		   char *newname)
6469 {
6470     LY_TEMP *p;
6471 
6472     CTRACE((tfp, "LYRenamedTemp(old=%s, new=%s)\n", oldname, newname));
6473     if ((p = FindTempfileByName(oldname)) != 0) {
6474 	StrAllocCopy((p->name), newname);
6475     }
6476 }
6477 
6478 #ifndef DISABLE_BIBP
6479 /*
6480  * Check that bibhost defines the BibP icon.
6481  */
LYCheckBibHost(void)6482 void LYCheckBibHost(void)
6483 {
6484     DocAddress bibhostIcon;
6485     BOOLEAN saveFlag;
6486 
6487     bibhostIcon.address = NULL;
6488     StrAllocCopy(bibhostIcon.address, BibP_bibhost);
6489     StrAllocCat(bibhostIcon.address, "bibp1.0/bibpicon.jpg");
6490     bibhostIcon.post_data = NULL;
6491     bibhostIcon.post_content_type = NULL;
6492     bibhostIcon.bookmark = FALSE;
6493     bibhostIcon.isHEAD = FALSE;
6494     bibhostIcon.safe = FALSE;
6495     saveFlag = traversal;
6496     traversal = TRUE;		/* Hack to force error response. */
6497     BibP_bibhost_available = (BOOLEAN) (HTLoadAbsolute(&bibhostIcon) == YES);
6498     traversal = saveFlag;
6499     BibP_bibhost_checked = TRUE;
6500 }
6501 #endif /* !DISABLE_BIBP */
6502 
6503 /*
6504  * Management of User Interface Pages.  - kw
6505  *
6506  * These are mostly temp files.  Pages which can be recognized by their special
6507  * URL (after having been loaded) need not be tracked here.
6508  *
6509  * First some private stuff:
6510  */
6511 typedef struct uipage_entry {
6512     UIP_t type;
6513     unsigned flags;
6514     char *url;
6515     HTList *alturls;
6516     char *file;
6517 } uip_entry;
6518 
6519 #define UIP_F_MULTI	0x0001	/* flag: track multiple instances */
6520 #define UIP_F_LIMIT	0x0002	/* flag: limit size of alturls list */
6521 #define UIP_F_LMULTI   (UIP_F_MULTI | UIP_F_LIMIT)
6522 /* *INDENT-OFF* */
6523 static uip_entry ly_uip[] =
6524 {
6525     { UIP_HISTORY		, UIP_F_LMULTI, NULL, NULL, NULL }
6526   , { UIP_DOWNLOAD_OPTIONS	, 0	      , NULL, NULL, NULL }
6527   , { UIP_PRINT_OPTIONS		, 0	      , NULL, NULL, NULL }
6528   , { UIP_SHOWINFO		, UIP_F_LMULTI, NULL, NULL, NULL }
6529   , { UIP_LIST_PAGE		, UIP_F_LMULTI, NULL, NULL, NULL }
6530   , { UIP_VLINKS		, UIP_F_LMULTI, NULL, NULL, NULL }
6531 #if !defined(NO_OPTION_FORMS)
6532   , { UIP_OPTIONS_MENU		, UIP_F_LMULTI, NULL, NULL, NULL }
6533 #endif
6534 #ifdef DIRED_SUPPORT
6535   , { UIP_DIRED_MENU		, 0	      , NULL, NULL, NULL }
6536   , { UIP_PERMIT_OPTIONS	, 0	      , NULL, NULL, NULL }
6537   , { UIP_UPLOAD_OPTIONS	, UIP_F_LMULTI, NULL, NULL, NULL }
6538 #endif
6539 #ifdef USE_ADDRLIST_PAGE
6540   , { UIP_ADDRLIST_PAGE		, UIP_F_LMULTI, NULL, NULL, NULL }
6541 #endif
6542   , { UIP_LYNXCFG		, UIP_F_LMULTI, NULL, NULL, NULL }
6543 #if !defined(NO_CONFIG_INFO)
6544   , { UIP_CONFIG_DEF		, UIP_F_LMULTI, NULL, NULL, NULL }
6545 #endif
6546 /* The following are not generated tempfiles: */
6547   , { UIP_TRACELOG		, 0	     , NULL, NULL, NULL }
6548 #if defined(DIRED_SUPPORT) && defined(OK_INSTALL)
6549   , { UIP_INSTALL		, 0	     , NULL, NULL, NULL }
6550 #endif
6551 
6552 };
6553 /* *INDENT-ON* */
6554 
6555 /*  Public entry points for User Interface Page management: */
6556 
LYIsUIPage3(const char * url,UIP_t type,int flagparam)6557 BOOL LYIsUIPage3(const char *url,
6558 		 UIP_t type,
6559 		 int flagparam)
6560 {
6561     unsigned int i;
6562     size_t l;
6563 
6564     if (!url)
6565 	return NO;
6566     for (i = 0; i < TABLESIZE(ly_uip); i++) {
6567 	if (ly_uip[i].type == type) {
6568 	    if (!ly_uip[i].url) {
6569 		return NO;
6570 	    } else if ((flagparam & UIP_P_FRAG) ?
6571 		       (!StrNCmp(ly_uip[i].url, url, (l = strlen(ly_uip[i].url)))
6572 			&& (url[l] == '\0' || url[l] == '#')) :
6573 		       !strcmp(ly_uip[i].url, url)) {
6574 		return YES;
6575 	    } else if (ly_uip[i].flags & UIP_F_MULTI) {
6576 		char *p;
6577 		HTList *l0 = ly_uip[i].alturls;
6578 
6579 		while ((p = (char *) HTList_nextObject(l0)) != NULL) {
6580 		    if ((flagparam & UIP_P_FRAG) ?
6581 			(!StrNCmp(p, url, (l = strlen(p)))
6582 			 && (url[l] == '\0' || url[l] == '#')) :
6583 			!strcmp(p, url))
6584 			return YES;
6585 		}
6586 	    }
6587 	    return NO;
6588 	}
6589     }
6590     return NO;
6591 }
6592 
LYRegisterUIPage(const char * url,UIP_t type)6593 void LYRegisterUIPage(const char *url,
6594 		      UIP_t type)
6595 {
6596     unsigned int i;
6597 
6598     for (i = 0; i < TABLESIZE(ly_uip); i++) {
6599 	if (ly_uip[i].type == type) {
6600 	    if (ly_uip[i].url && url &&
6601 		!strcmp(ly_uip[i].url, url)) {
6602 
6603 	    } else if (!ly_uip[i].url || !url ||
6604 		       !(ly_uip[i].flags & UIP_F_MULTI)) {
6605 		StrAllocCopy(ly_uip[i].url, url);
6606 
6607 	    } else {
6608 		char *p;
6609 		int n = 0;
6610 		HTList *l0 = ly_uip[i].alturls;
6611 
6612 		while ((p = (char *) HTList_nextObject(l0)) != NULL) {
6613 		    if (!strcmp(p, url))
6614 			return;
6615 		    if (!strcmp(p, ly_uip[i].url)) {
6616 			StrAllocCopy(ly_uip[i].url, url);
6617 			return;
6618 		    }
6619 		    n++;
6620 		}
6621 		if (!ly_uip[i].alturls)
6622 		    ly_uip[i].alturls = HTList_new();
6623 
6624 		if (n >= HTCacheSize && (ly_uip[i].flags & UIP_F_LIMIT))
6625 		    HTList_removeFirstObject(ly_uip[i].alturls);
6626 		HTList_addObject(ly_uip[i].alturls, ly_uip[i].url);
6627 		ly_uip[i].url = NULL;
6628 		StrAllocCopy(ly_uip[i].url, url);
6629 	    }
6630 
6631 	    return;
6632 	}
6633     }
6634 }
6635 
LYUIPages_free(void)6636 void LYUIPages_free(void)
6637 {
6638     unsigned int i;
6639 
6640     for (i = 0; i < TABLESIZE(ly_uip); i++) {
6641 	FREE(ly_uip[i].url);
6642 	FREE(ly_uip[i].file);
6643 	LYFreeStringList(ly_uip[i].alturls);
6644 	ly_uip[i].alturls = NULL;
6645     }
6646 }
6647 
6648 /*
6649  * Convert local pathname to www name
6650  * (do not bother about file://localhost prefix at this point).
6651  */
wwwName(const char * pathname)6652 const char *wwwName(const char *pathname)
6653 {
6654     const char *cp = NULL;
6655 
6656 #if defined(USE_DOS_DRIVES)
6657     cp = HTDOS_wwwName(pathname);
6658 #else
6659 #ifdef VMS
6660     cp = HTVMS_wwwName(pathname);
6661 #else
6662     cp = pathname;
6663 #endif /* VMS */
6664 #endif
6665 
6666     return cp;
6667 }
6668 
6669 /*
6670  * Given a user-specified filename, e.g., for download or print, validate and
6671  * expand it.  Expand home-directory expressions in the given string.  Only
6672  * allow pipes if the user can spawn shell commands.
6673  */
LYValidateFilename(bstring ** result,bstring ** given)6674 BOOLEAN LYValidateFilename(bstring **result,
6675 			   bstring **given)
6676 {
6677     BOOLEAN code = TRUE;
6678     char *cp = NULL;
6679 
6680     /*
6681      * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path on VMS.
6682      * - FM
6683      */
6684     if (LYIsNullDevice((*given)->str)) {
6685 	/* just ignore it */
6686 	code = FALSE;
6687 #ifdef HAVE_POPEN
6688     } else if (LYIsPipeCommand((*given)->str)) {
6689 	if (no_shell) {
6690 	    HTUserMsg(SPAWNING_DISABLED);
6691 	    code = FALSE;
6692 	} else {
6693 	    BStrCopy(*result, (*given));
6694 	}
6695 #endif
6696     } else {
6697 	if (FindLeadingTilde((*given)->str, TRUE) != 0) {
6698 	    char *cp1 = NULL;
6699 
6700 	    StrAllocCopy(cp1, (*given)->str);
6701 	    LYTildeExpand(&cp1, TRUE);
6702 	    BStrCopy0(*result, cp1);
6703 	    BStrCopy0(*given, cp1);
6704 	    FREE(cp1);
6705 	}
6706 #ifdef VMS
6707 	if (strchr((*given)->str, '/') != NULL) {
6708 	    BStrCopy0(*result, HTVMS_name("", (*given)->str));
6709 	    BStrCopy(*given, *result);
6710 	}
6711 	if ((*given)->str[0] != '/'
6712 	    && strchr((*given)->str, ':') == NULL) {
6713 	    BStrCopy0(*result, "sys$disk:");
6714 	    if (strchr((*given)->str, ']') == NULL)
6715 		BStrCat0(*result, "[]");
6716 	    BStrCat(*result, (*given));
6717 	} else {
6718 	    BStrCopy(*result, (*given));
6719 	}
6720 #else
6721 
6722 #ifndef __EMX__
6723 	if (!LYisAbsPath((*given)->str)) {
6724 #if defined(__DJGPP__) || defined(_WINDOWS)
6725 	    if (strchr((*result)->str, ':') != NULL)
6726 		cp = NULL;
6727 	    else
6728 #endif /*  __DJGPP__ || _WINDOWS */
6729 	    {
6730 #ifdef SUPPORT_CHDIR
6731 		static char buf[LY_MAXPATH];
6732 
6733 		cp = Current_Dir(buf);
6734 #else
6735 		cp = original_dir;
6736 #endif
6737 	    }
6738 	} else
6739 #endif /* __EMX__ */
6740 	    cp = NULL;
6741 
6742 	if (cp) {
6743 	    LYTrimPathSep(cp);
6744 	    BStrCopy0(*result, cp);
6745 	    BStrCat0(*result, "/");
6746 	} else {
6747 	    BStrCopy0(*result, "");
6748 	}
6749 	if (code) {
6750 	    cp = HTSYS_name((*given)->str);
6751 	    BStrCat0(*result, cp);
6752 	}
6753 #endif /* VMS */
6754     }
6755     return code;
6756 }
6757 
6758 /*
6759  * Given a valid filename, check if it exists.  If so, we'll have to worry
6760  * about overwriting it.
6761  *
6762  * Returns:
6763  *	'Y' (yes/success)
6764  *	'N' (no/retry)
6765  *	3   (cancel)
6766  */
LYValidateOutput(char * filename)6767 int LYValidateOutput(char *filename)
6768 {
6769     int c;
6770 
6771     /*
6772      * Assume we can write to a pipe
6773      */
6774 #ifdef HAVE_POPEN
6775     if (LYIsPipeCommand(filename))
6776 	return 'Y';
6777 #endif
6778 
6779     if (no_dotfiles || !show_dotfiles) {
6780 	if (*LYPathLeaf(filename) == '.') {
6781 	    HTAlert(FILENAME_CANNOT_BE_DOT);
6782 	    return 'N';
6783 	}
6784     }
6785 
6786     /*
6787      * See if it already exists.
6788      */
6789     if (LYCanReadFile(filename)) {
6790 #ifdef VMS
6791 	c = HTConfirm(FILE_EXISTS_HPROMPT);
6792 #else
6793 	c = HTConfirm(FILE_EXISTS_OPROMPT);
6794 #endif /* VMS */
6795 	if (HTLastConfirmCancelled()) {
6796 	    HTInfoMsg(SAVE_REQUEST_CANCELLED);
6797 	    return 3;
6798 	} else if (c == NO) {
6799 	    return 'N';
6800 	}
6801     } else if (!LYCanWriteFile(filename)) {
6802 	return 'N';
6803     }
6804     return 'Y';
6805 }
6806 
6807 /*
6808  * Convert a local filename to a URL
6809  */
LYLocalFileToURL(char ** target,const char * source)6810 void LYLocalFileToURL(char **target,
6811 		      const char *source)
6812 {
6813     const char *leaf;
6814 
6815     StrAllocCopy(*target, "file://localhost");
6816 
6817     leaf = wwwName(source);
6818 
6819     if (!LYisAbsPath(source)) {
6820 	char temp[LY_MAXPATH];
6821 
6822 	Current_Dir(temp);
6823 	if (!LYIsHtmlSep(*temp))
6824 	    LYAddHtmlSep(target);
6825 	StrAllocCat(*target, temp);
6826     }
6827     if (leaf && !LYIsHtmlSep(*leaf))
6828 	LYAddHtmlSep(target);
6829     StrAllocCat(*target, leaf);
6830 }
6831 
6832 #define MY_DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
6833 #define PUT_STRING(buf)    (*(target)->isa->put_string)(target, buf)
6834 
6835 /*
6836  * Like WriteInternalTitle, used for writing title on pages constructed via
6837  * streams.
6838  */
WriteStreamTitle(HTStream * target,const char * Title)6839 void WriteStreamTitle(HTStream *target, const char *Title)
6840 {
6841     char *buf = 0;
6842 
6843     PUT_STRING(MY_DOCTYPE);
6844     PUT_STRING("<html>\n<head>\n");
6845     LYAddMETAcharsetToStream(target, -1);
6846     HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n", Title);
6847     PUT_STRING(buf);
6848     FREE(buf);
6849 }
6850 
6851 /*
6852  * Open a temporary file for internal-pages, optionally reusing an existing
6853  * filename.
6854  */
InternalPageFP(char * filename,int reuse_flag)6855 FILE *InternalPageFP(char *filename,
6856 		     int reuse_flag)
6857 {
6858     FILE *fp;
6859 
6860     if (LYReuseTempfiles && reuse_flag) {
6861 	fp = LYOpenTempRewrite(filename, HTML_SUFFIX, BIN_W);
6862     } else {
6863 	(void) LYRemoveTemp(filename);
6864 	fp = LYOpenTemp(filename, HTML_SUFFIX, BIN_W);
6865     }
6866     if (fp == NULL) {
6867 	HTAlert(CANNOT_OPEN_TEMP);
6868     }
6869     return fp;
6870 }
6871 
6872 /*
6873  * This part is shared by all internal pages.
6874  */
WriteInternalTitle(FILE * fp0,const char * Title)6875 void WriteInternalTitle(FILE *fp0, const char *Title)
6876 {
6877     fprintf(fp0, MY_DOCTYPE);
6878 
6879     fprintf(fp0, "<html>\n<head>\n");
6880     LYAddMETAcharsetToFD(fp0, -1);
6881     if (LYIsListpageTitle(Title)) {
6882 	if (strchr(HTLoadedDocumentURL(), '"') == NULL) {
6883 	    char *Address = NULL;
6884 
6885 	    /*
6886 	     * Insert a BASE tag so there is some way to relate the List Page
6887 	     * file to its underlying document after we are done.  It won't be
6888 	     * actually used for resolving relative URLs.  - kw
6889 	     */
6890 	    StrAllocCopy(Address, HTLoadedDocumentURL());
6891 	    LYEntify(&Address, FALSE);
6892 	    fprintf(fp0, "<base href=\"%s\">\n", Address);
6893 	    FREE(Address);
6894 	}
6895     }
6896     fprintf(fp0, "<title>%s</title>\n</head>\n<body>\n", Title);
6897 }
6898 
6899 /*
6900  * This is used to start most internal pages, except for special cases where
6901  * the embedded HREFs in the title differ.
6902  */
BeginInternalPage(FILE * fp0,const char * Title,const char * HelpURL)6903 void BeginInternalPage(FILE *fp0, const char *Title,
6904 		       const char *HelpURL)
6905 {
6906     WriteInternalTitle(fp0, Title);
6907 
6908     if ((user_mode == NOVICE_MODE)
6909 	&& LYwouldPush(Title, NULL)
6910 	&& (HelpURL != 0)) {
6911 	fprintf(fp0, "<h1>%s (%s%s%s), <a href=\"%s%s\">help</a></h1>\n",
6912 		Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION,
6913 		helpfilepath, HelpURL);
6914     } else {
6915 	fprintf(fp0, "<h1>%s (%s%s%s)</h1>\n",
6916 		Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION);
6917     }
6918 }
6919 
EndInternalPage(FILE * fp0)6920 void EndInternalPage(FILE *fp0)
6921 {
6922     fprintf(fp0, "</body>\n</html>");
6923 }
6924 
trimPoundSelector(char * address)6925 char *trimPoundSelector(char *address)
6926 {
6927     char *pound = findPoundSelector(address);
6928 
6929     if (pound != 0)
6930 	*pound = '\0';
6931     return pound;
6932 }
6933 
6934 /*
6935  * Trim a trailing path-separator to avoid confusing other programs when we concatenate
6936  * to it.  This only applies to local filesystems.
6937  */
LYTrimPathSep(char * path)6938 void LYTrimPathSep(char *path)
6939 {
6940     size_t len;
6941 
6942     if (path != 0
6943 	&& (len = strlen(path)) != 0
6944 	&& LYIsPathSep(path[len - 1]))
6945 	path[len - 1] = 0;
6946 }
6947 
6948 /*
6949  * Add a trailing path-separator to avoid confusing other programs when we concatenate
6950  * to it.  This only applies to local filesystems.
6951  */
LYAddPathSep(char ** path)6952 void LYAddPathSep(char **path)
6953 {
6954     size_t len;
6955     char *temp;
6956 
6957     if ((path != 0)
6958 	&& ((temp = *path) != 0)
6959 	&& (len = strlen(temp)) != 0
6960 	&& !LYIsPathSep(temp[len - 1])) {
6961 	StrAllocCat(*path, PATHSEP_STR);
6962     }
6963 }
6964 
6965 /*
6966  * Add a trailing path-separator to avoid confusing other programs when we concatenate
6967  * to it.  This only applies to local filesystems.
6968  */
LYAddPathSep0(char * path)6969 void LYAddPathSep0(char *path)
6970 {
6971     size_t len;
6972 
6973     if ((path != 0)
6974 	&& (len = strlen(path)) != 0
6975 	&& (len < LY_MAXPATH - 2)
6976 	&& !LYIsPathSep(path[len - 1])) {
6977 	strcat(path, PATHSEP_STR);
6978     }
6979 }
6980 
6981 /*
6982  * Check if a given string contains a path separator
6983  */
LYLastPathSep(const char * path)6984 char *LYLastPathSep(const char *path)
6985 {
6986     char *result;
6987 
6988 #if defined(USE_DOS_DRIVES)
6989     if ((result = strrchr(path, '\\')) == 0)
6990 	result = strrchr(path, '/');
6991 #else
6992     result = strrchr(path, '/');
6993 #endif
6994     return result;
6995 }
6996 
6997 /*
6998  * Trim a trailing path-separator to avoid confusing other programs when we concatenate
6999  * to it.  This only applies to HTML paths.
7000  */
LYTrimHtmlSep(char * path)7001 void LYTrimHtmlSep(char *path)
7002 {
7003     size_t len;
7004 
7005     if (path != 0
7006 	&& (len = strlen(path)) != 0
7007 	&& LYIsHtmlSep(path[len - 1]))
7008 	path[len - 1] = 0;
7009 }
7010 
7011 /*
7012  * Add a trailing path-separator to avoid confusing other programs when we concatenate
7013  * to it.  This only applies to HTML paths.
7014  */
LYAddHtmlSep(char ** path)7015 void LYAddHtmlSep(char **path)
7016 {
7017     size_t len;
7018     char *temp;
7019 
7020     if ((path != 0)
7021 	&& ((temp = *path) != 0)
7022 	&& (len = strlen(temp)) != 0
7023 	&& !LYIsHtmlSep(temp[len - 1])) {
7024 	StrAllocCat(*path, "/");
7025     }
7026 }
7027 
7028 /*
7029  * Add a trailing path-separator to avoid confusing other programs when we concatenate
7030  * to it.  This only applies to HTML paths.
7031  */
LYAddHtmlSep0(char * path)7032 void LYAddHtmlSep0(char *path)
7033 {
7034     size_t len;
7035 
7036     if ((path != 0)
7037 	&& (len = strlen(path)) != 0
7038 	&& (len < LY_MAXPATH - 2)
7039 	&& !LYIsHtmlSep(path[len - 1])) {
7040 	strcat(path, "/");
7041     }
7042 }
7043 
7044 /*
7045  * Copy a file
7046  */
LYCopyFile(char * src,char * dst)7047 int LYCopyFile(char *src,
7048 	       char *dst)
7049 {
7050     int code;
7051     const char *program;
7052 
7053     if ((program = HTGetProgramPath(ppCOPY)) != NULL) {
7054 	char *the_command = 0;
7055 
7056 	HTAddParam(&the_command, COPY_COMMAND, 1, program);
7057 	HTAddParam(&the_command, COPY_COMMAND, 2, src);
7058 	HTAddParam(&the_command, COPY_COMMAND, 3, dst);
7059 	HTEndParam(&the_command, COPY_COMMAND, 3);
7060 
7061 	CTRACE((tfp, "command: %s\n", the_command));
7062 	stop_curses();
7063 	code = LYSystem(the_command);
7064 	start_curses();
7065 
7066 	FREE(the_command);
7067     } else {
7068 	FILE *fin, *fout;
7069 	unsigned char buff[BUFSIZ];
7070 	size_t len;
7071 
7072 	code = EOF;
7073 	if ((fin = fopen(src, BIN_R)) != 0) {
7074 	    if ((fout = fopen(dst, BIN_W)) != 0) {
7075 		code = 0;
7076 		while ((len = fread(buff, (size_t) 1, sizeof(buff), fin)) != 0) {
7077 		    if (fwrite(buff, (size_t) 1, len, fout) < len
7078 			|| ferror(fout)) {
7079 			code = EOF;
7080 			break;
7081 		    }
7082 		}
7083 		LYCloseOutput(fout);
7084 	    }
7085 	    LYCloseInput(fin);
7086 	}
7087 	CTRACE((tfp, "builtin copy ->%d\n\tsource=%s\n\ttarget=%s\n",
7088 		code, src, dst));
7089     }
7090 
7091     if (code) {
7092 	HTAlert(CANNOT_WRITE_TO_FILE);
7093     }
7094     return code;
7095 }
7096 
7097 #ifdef __DJGPP__
escape_backslashes(char * source)7098 static char *escape_backslashes(char *source)
7099 {
7100     char *result = 0;
7101     int count = 0;
7102     int n;
7103 
7104     for (n = 0; source[n] != '\0'; ++n) {
7105 	if (source[n] == '\\')
7106 	    ++count;
7107     }
7108     if (count != 0) {
7109 	result = malloc(count + n + 1);
7110 	if (result != 0) {
7111 	    int ch;
7112 	    char *target = result;
7113 
7114 	    while ((ch = *source++) != '\0') {
7115 		if (ch == '\\')
7116 		    *target++ = ch;
7117 		*target++ = ch;
7118 	    }
7119 	    *target = '\0';
7120 	}
7121     }
7122     return result;
7123 }
7124 #endif /* __DJGPP__ */
7125 /*
7126  * Invoke a shell command, return nonzero on error.
7127  */
LYSystem(char * command)7128 int LYSystem(char *command)
7129 {
7130     int code;
7131     int do_free = 0;
7132 
7133 #if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
7134     struct sigaction saved_sigtstp_act;
7135     BOOLEAN sigtstp_saved = FALSE;
7136 #endif
7137     int saved_errno = 0;
7138 
7139 #ifdef __EMX__
7140     int scrsize[4];
7141 #endif
7142 
7143     fflush(stdout);
7144     fflush(stderr);
7145     CTRACE((tfp, "LYSystem(%s)\n", command));
7146     CTRACE_FLUSH(tfp);
7147 
7148 #ifdef __DJGPP__
7149     __djgpp_set_ctrl_c(0);
7150     _go32_want_ctrl_break(1);
7151 #endif /* __DJGPP__ */
7152 
7153 #ifdef VMS
7154     code = DCLsystem(command);
7155 #else
7156 #  ifdef __EMX__		/* FIXME: Should be LY_CONVERT_SLASH? */
7157     /* Configure writes commands which contain direct slashes.
7158        Native command-(non)-shell will not tolerate this. */
7159     {
7160 	char *space = command, *slash = command;
7161 
7162 	_scrsize(scrsize);
7163 	while (*space && *space != ' ' && *space != '\t')
7164 	    space++;
7165 	while (slash < space && *slash != '/')
7166 	    slash++;
7167 	if (slash != space) {
7168 	    char *old = command;
7169 
7170 	    command = NULL;
7171 	    StrAllocCopy(command, old);
7172 	    do_free = 1;
7173 	    slash = (slash - old) + command - 1;
7174 	    space = (space - old) + command;
7175 	    while (++slash < space)
7176 		if (*slash == '/')
7177 		    *slash = '\\';
7178 	}
7179     }
7180 #  endif
7181 
7182     /*
7183      * This chunk of code does not work, for two reasons:
7184      * a) the Cygwin system() function edits out the backslashes
7185      * b) it does not account for more than one parameter, e.g., +number
7186      */
7187 #if defined(__CYGWIN__) && defined(DOSPATH)	/* 1999/02/26 (Fri) */
7188     {
7189 	char cmd[LY_MAXPATH];
7190 	char win32_name[LY_MAXPATH];
7191 	char new_cmd[LY_MAXPATH];
7192 	char new_command[LY_MAXPATH * 2 + 10];
7193 	char *p, *q;
7194 
7195 	p = command;
7196 	q = cmd;
7197 	while (*p) {
7198 	    if (*p == ' ')
7199 		break;
7200 	    else
7201 		*q = *p;
7202 	    p++;
7203 	    q++;
7204 	}
7205 	*q = '\0';
7206 
7207 	if (cmd[0] == '/')
7208 	    cygwin_conv_to_full_posix_path(cmd, new_cmd);
7209 	else
7210 	    strcpy(new_cmd, cmd);
7211 
7212 	while (*p == ' ')
7213 	    p++;
7214 
7215 	if (strchr(p, '\\') == NULL) {
7216 	    /* for Windows Application */
7217 	    cygwin_conv_to_full_win32_path(p, win32_name);
7218 	    sprintf(new_command, "%.*s \"%.*s\"",
7219 		    LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
7220 	} else {
7221 	    /* for DOS like editor */
7222 	    q = win32_name;
7223 	    while (*p) {
7224 		if (*p == '\\') {
7225 		    if (*(p + 1) == '\\')
7226 			p++;
7227 		}
7228 		*q = *p;
7229 		q++, p++;
7230 	    }
7231 	    *q = '\0';
7232 	    sprintf(new_command, "%.*s %.*s", LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
7233 	}
7234 	command = new_command;
7235     }
7236 #endif
7237 
7238 #ifdef __DJGPP__
7239     if (dj_is_bash) {
7240 	char *new_command = escape_backslashes(command);
7241 
7242 	if (new_command != 0) {
7243 	    if (do_free)
7244 		free(command);
7245 	    command = new_command;
7246 	}
7247     }
7248 #endif /* __DJGPP__ */
7249 
7250 #ifdef _WIN_CC
7251     code = exec_command(command, TRUE);		/* Wait exec */
7252 #else /* !_WIN_CC */
7253 #ifdef SIGPIPE
7254     if (restore_sigpipe_for_children)
7255 	signal(SIGPIPE, SIG_DFL);	/* Some commands expect the default */
7256 #endif
7257 #if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
7258     if (!dump_output_immediately && !LYCursesON && !no_suspend)
7259 	sigtstp_saved = LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 1);
7260 #endif
7261     code = system(command);
7262     saved_errno = errno;
7263 #if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
7264     if (sigtstp_saved)
7265 	LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 0);
7266 #endif
7267 #ifdef SIGPIPE
7268     if (restore_sigpipe_for_children)
7269 	signal(SIGPIPE, SIG_IGN);	/* Ignore it again - kw */
7270 #endif
7271 #endif
7272 #endif
7273 
7274 #ifdef __DJGPP__
7275     __djgpp_set_ctrl_c(1);
7276     _go32_want_ctrl_break(0);
7277 #endif /* __DJGPP__ */
7278 
7279     fflush(stdout);
7280     fflush(stderr);
7281 
7282     if (do_free)
7283 	FREE(command);
7284 #if !defined(UCX) || !defined(VAXC)	/* errno not modifiable ?? */
7285     set_errno(saved_errno);	/* may have been clobbered */
7286 #endif
7287 #ifdef __EMX__			/* Check whether the screen size changed */
7288     size_change(0);
7289 #endif
7290     return code;
7291 }
7292 
7293 /*
7294  * Return a string which can be used in LYSystem() for spawning a subshell
7295  */
7296 #if defined(__CYGWIN__)		/* 1999/02/26 (Fri) */
Cygwin_Shell(void)7297 int Cygwin_Shell(void)
7298 {
7299     char *shell;
7300     int code;
7301     STARTUPINFO startUpInfo;
7302     PROCESS_INFORMATION procInfo;
7303     SECURITY_ATTRIBUTES sa;
7304 
7305     /* Set up security attributes to allow inheritance of the file handle */
7306 
7307     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
7308     sa.lpSecurityDescriptor = 0;
7309     sa.bInheritHandle = TRUE;
7310 
7311     /* Init a startup structure */
7312     GetStartupInfo(&startUpInfo);
7313 
7314     shell = LYGetEnv("COMSPEC");
7315 
7316     /* Create the child process, specifying
7317        inherited handles. Pass the value of the
7318        handle as a command line parameter */
7319     code = 0;
7320     if (shell) {
7321 	code = CreateProcess(0, shell, 0, 0,
7322 			     TRUE, 0,
7323 			     0, 0, &startUpInfo, &procInfo);
7324 
7325 	if (!code) {
7326 	    printf("shell = [%s], code = %ld\n", shell, (long) GetLastError());
7327 	}
7328 
7329 	/* wait for the child to return (this is not a requirement
7330 	   since the child is its own independent process) */
7331 	WaitForSingleObject(procInfo.hProcess, INFINITE);
7332     }
7333 
7334     return code;
7335 }
7336 #endif
7337 
7338 #ifdef WIN_EX
7339 /*
7340  * Quote the path to make it safe for shell command processing.
7341  *  We always quote it not only includes spaces in it.
7342  *  At least we should quote paths which include "&".
7343  */
quote_pathname(char * pathname)7344 char *quote_pathname(char *pathname)
7345 {
7346     char *result = NULL;
7347 
7348     HTSprintf0(&result, "\"%s\"", pathname);
7349     return result;
7350 }
7351 #endif
7352 
LYSysShell(void)7353 const char *LYSysShell(void)
7354 {
7355     const char *shell = 0;
7356 
7357 #ifdef DOSPATH
7358 #ifdef WIN_EX
7359     shell = LYGetEnv("SHELL");
7360     if (shell) {
7361 	if (access(shell, 0) != 0)
7362 	    shell = LYGetEnv("COMSPEC");
7363     } else {
7364 	shell = LYGetEnv("COMSPEC");
7365     }
7366     if (shell == NULL) {
7367 	if (system_is_NT)
7368 	    shell = "cmd.exe";
7369 	else
7370 	    shell = "command.com";
7371     }
7372 #else
7373     shell = LYGetEnv("SHELL");
7374     if (shell == NULL) {
7375 	shell = LYGetEnv("COMSPEC");
7376     }
7377     if (shell == NULL) {
7378 	shell = "command.com";
7379     }
7380 #endif /* WIN_EX */
7381 #else
7382 #ifdef __EMX__
7383     if (LYGetEnv("SHELL") != NULL) {
7384 	shell = LYGetEnv("SHELL");
7385     } else {
7386 	shell = (LYGetEnv("COMSPEC") == NULL) ? "cmd.exe" : LYGetEnv("COMSPEC");
7387     }
7388 #else
7389 #ifdef VMS
7390     shell = "";
7391 #else
7392     shell = "exec $SHELL";
7393 #endif /* __EMX__ */
7394 #endif /* VMS */
7395 #endif /* DOSPATH */
7396     return shell;
7397 }
7398 
7399 #ifdef VMS
7400 #define DISPLAY "DECW$DISPLAY"
7401 #else
7402 #define DISPLAY "DISPLAY"
7403 #endif /* VMS */
7404 
7405 /*
7406  * Return the X-Window $DISPLAY string if it is nonnull/nonempty
7407  */
LYgetXDisplay(void)7408 char *LYgetXDisplay(void)
7409 {
7410     return LYGetEnv(DISPLAY);
7411 }
7412 
7413 /*
7414  * Set the value of the X-Window $DISPLAY variable (yes it leaks memory, but
7415  * that is putenv's fault).
7416  */
LYsetXDisplay(char * new_display)7417 void LYsetXDisplay(char *new_display)
7418 {
7419     if (new_display != 0) {
7420 #ifdef VMS
7421 	LYUpperCase(new_display);
7422 	Define_VMSLogical(DISPLAY, new_display);
7423 #else
7424 	static char *display_putenv_command;
7425 
7426 	HTSprintf0(&display_putenv_command, "DISPLAY=%s", new_display);
7427 	putenv(display_putenv_command);
7428 #endif /* VMS */
7429 	if ((new_display = LYgetXDisplay()) != 0) {
7430 	    StrAllocCopy(x_display, new_display);
7431 	}
7432     }
7433 }
7434 
7435 #ifdef CAN_CUT_AND_PASTE
7436 #ifdef __EMX__
7437 
7438 static int proc_type = -1;
7439 static PPIB pib;
7440 static HAB hab;
7441 static HMQ hmq;
7442 
morph_PM(void)7443 static void morph_PM(void)
7444 {
7445     PTIB tib;
7446     int first = 0;
7447 
7448     if (proc_type == -1) {
7449 	DosGetInfoBlocks(&tib, &pib);
7450 	proc_type = pib->pib_ultype;
7451 	first = 1;
7452     }
7453     if (pib->pib_ultype != 3)	/* 2 is VIO */
7454 	pib->pib_ultype = 3;	/* 3 is PM */
7455     if (first)
7456 	hab = WinInitialize(0);
7457     /* 64 messages if before OS/2 3.0, ignored otherwise */
7458     hmq = WinCreateMsgQueue(hab, 64);
7459     WinCancelShutdown(hmq, 1);	/* Do not inform us on shutdown */
7460 }
7461 
unmorph_PM(void)7462 static void unmorph_PM(void)
7463 {
7464     WinDestroyMsgQueue(hmq);
7465     pib->pib_ultype = proc_type;
7466 }
7467 
size_clip(void)7468 int size_clip(void)
7469 {
7470     return 8192;
7471 }
7472 
7473 /* Code partially stolen from FED editor. */
7474 
put_clip(const char * s)7475 int put_clip(const char *s)
7476 {
7477     int sz = strlen(s) + 1;
7478     int ret = EOF, nl = 0;
7479     char *pByte = 0, *s1 = s, c, *t;
7480 
7481     while ((c = *s1++)) {
7482 	if (c == '\r' && *s1 == '\n')
7483 	    s1++;
7484 	else if (c == '\n')
7485 	    nl++;
7486     }
7487     if (DosAllocSharedMem((PPVOID) & pByte, 0, sz + nl,
7488 			  PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE | OBJ_GETTABLE))
7489 	return ret;
7490 
7491     if (!nl)
7492 	memcpy(pByte, s, sz);
7493     else {
7494 	t = pByte;
7495 	while ((c = *t++ = *s++))
7496 	    if (c == '\n' && (t == pByte + 1 || t[-2] != '\r'))
7497 		t[-1] = '\r', *t++ = '\n';
7498     }
7499 
7500     morph_PM();
7501     if (!hab)
7502 	goto fail;
7503 
7504     WinOpenClipbrd(hab);
7505     WinEmptyClipbrd(hab);
7506     if (WinSetClipbrdData(hab, (ULONG) pByte, CF_TEXT, CFI_POINTER))
7507 	ret = 0;
7508     WinCloseClipbrd(hab);
7509     unmorph_PM();
7510     if (ret == 0)
7511 	return 0;
7512   fail:
7513     DosFreeMem((PPVOID) & pByte);
7514     return EOF;
7515 }
7516 
7517 static int clip_open;
7518 
7519 /* get_clip_grab() returns a pointer to the string in the system area.
7520    get_clip_release() should be called ASAP after this. */
7521 
get_clip_grab(void)7522 char *get_clip_grab(void)
7523 {
7524     char *ClipData;
7525     ULONG ulFormat;
7526     int sz;
7527 
7528     morph_PM();
7529     if (!hab)
7530 	return 0;
7531     if (clip_open)
7532 	get_clip_release();
7533 
7534     WinQueryClipbrdFmtInfo(hab, CF_TEXT, &ulFormat);
7535     if (ulFormat != CFI_POINTER) {
7536 	unmorph_PM();
7537 	return 0;
7538     }
7539     WinOpenClipbrd(hab);
7540     clip_open = 1;
7541     ClipData = (char *) WinQueryClipbrdData(hab, CF_TEXT);
7542     sz = strlen(ClipData);
7543     if (!ClipData || !sz) {
7544 	get_clip_release();
7545 	return 0;
7546     }
7547     return ClipData;
7548 }
7549 
get_clip_release(void)7550 void get_clip_release(void)
7551 {
7552     if (!clip_open)
7553 	return;
7554     WinCloseClipbrd(hab);
7555     clip_open = 0;
7556     unmorph_PM();
7557 }
7558 
7559 #else /* !( defined __EMX__ ) */
7560 
7561 #  if !defined(WIN_EX) && defined(HAVE_POPEN)
7562 
7563 static FILE *paste_handle = 0;
7564 static char *paste_buf = NULL;
7565 
get_clip_release(void)7566 void get_clip_release(void)
7567 {
7568     if (paste_handle != 0)
7569 	pclose(paste_handle);
7570     if (paste_buf)
7571 	FREE(paste_buf);
7572 }
7573 
clip_grab(void)7574 static int clip_grab(void)
7575 {
7576     char *cmd = LYGetEnv("RL_PASTE_CMD");
7577 
7578     if (paste_handle)
7579 	pclose(paste_handle);
7580     if (!cmd)
7581 	return 0;
7582 
7583     paste_handle = popen(cmd, TXT_R);
7584     if (!paste_handle)
7585 	return 0;
7586     return 1;
7587 }
7588 
7589 #define PASTE_BUFFER 1008
7590 #define CF_TEXT 0		/* Not used */
7591 
get_clip_grab(void)7592 char *get_clip_grab(void)
7593 {
7594     int len;
7595     unsigned size = PASTE_BUFFER;
7596     int off = 0;
7597 
7598     if (!clip_grab())
7599 	return NULL;
7600     if (!paste_handle)
7601 	return NULL;
7602     if (paste_buf)
7603 	FREE(paste_buf);
7604     paste_buf = typeMallocn(char, PASTE_BUFFER);
7605 
7606     while (1) {
7607 	len = (int) fread(paste_buf + off,
7608 			  (size_t) 1,
7609 			  (size_t) PASTE_BUFFER - 1,
7610 			  paste_handle);
7611 	paste_buf[off + len] = '\0';
7612 	if (len < PASTE_BUFFER - 1)
7613 	    break;
7614 	if (strchr(paste_buf + off, '\r')
7615 	    || strchr(paste_buf + off, '\n'))
7616 	    break;
7617 	paste_buf = typeRealloc(char, paste_buf, size += PASTE_BUFFER - 1);
7618 
7619 	off += len;
7620     }
7621     return paste_buf;
7622 }
7623 
put_clip(const char * s)7624 int put_clip(const char *s)
7625 {
7626     char *cmd = LYGetEnv("RL_CLCOPY_CMD");
7627     FILE *fh;
7628     size_t l = strlen(s), res;
7629 
7630     if (!cmd)
7631 	return -1;
7632 
7633     fh = popen(cmd, TXT_W);
7634     if (!fh)
7635 	return -1;
7636     res = fwrite(s, (size_t) 1, l, fh);
7637     if (pclose(fh) != 0 || res != l)
7638 	return -1;
7639     return 0;
7640 }
7641 
7642 #  endif /* !defined(WIN_EX) && defined(HAVE_POPEN) */
7643 
7644 #endif /* __EMX__ */
7645 
7646 #define WANT_LYmsec_delay
7647 
7648 #if defined(WIN_EX)		/* 1997/10/16 (Thu) 20:13:28 */
7649 
put_clip(const char * szBuffer)7650 int put_clip(const char *szBuffer)
7651 {
7652     HANDLE hWnd;
7653     HANDLE m_hLogData;
7654     LPTSTR pLogData;
7655     HANDLE hClip;
7656     int len;
7657 
7658     if (szBuffer == NULL)
7659 	return EOF;
7660 
7661     len = strlen(szBuffer);
7662     if (len == 0)
7663 	return EOF;
7664     else
7665 	len++;
7666 
7667     m_hLogData = GlobalAlloc(GHND, len);
7668     if (m_hLogData == NULL) {
7669 	return EOF;
7670     }
7671 
7672     hWnd = NULL;
7673     if (!OpenClipboard(hWnd)) {
7674 	return EOF;
7675     }
7676     /* Remove the current Clipboard contents */
7677     if (!EmptyClipboard()) {
7678 	GlobalFree(m_hLogData);
7679 	return EOF;
7680     }
7681 
7682     /* Lock the global memory while we write to it. */
7683     pLogData = (LPTSTR) GlobalLock(m_hLogData);
7684 
7685     lstrcpy((LPTSTR) pLogData, szBuffer);
7686     GlobalUnlock(m_hLogData);
7687 
7688     /* If there were any lines at all then copy them to clipboard. */
7689     hClip = SetClipboardData(CF_TEXT, m_hLogData);
7690     if (!hClip) {
7691 	/* If we couldn't clip the data then free the global handle. */
7692 	GlobalFree(m_hLogData);
7693     }
7694 
7695     CloseClipboard();
7696     return 0;
7697 }
7698 
7699 static HANDLE m_hLogData;
7700 static int m_locked;
7701 
7702 /* get_clip_grab() returns a pointer to the string in the system area.
7703    get_clip_release() should be called ASAP after this. */
7704 
get_clip_grab()7705 char *get_clip_grab()
7706 {
7707     HANDLE hWnd;
7708     LPTSTR pLogData;
7709 
7710     hWnd = NULL;
7711     if (!OpenClipboard(hWnd)) {
7712 	return 0;
7713     }
7714 
7715     m_hLogData = GetClipboardData(CF_TEXT);
7716 
7717     if (m_hLogData == NULL) {
7718 	CloseClipboard();
7719 	m_locked = 0;
7720 	return 0;
7721     }
7722     pLogData = (LPTSTR) GlobalLock(m_hLogData);
7723 
7724     m_locked = 1;
7725     return pLogData;
7726 }
7727 
get_clip_release()7728 void get_clip_release()
7729 {
7730     if (!m_locked)
7731 	return;
7732     GlobalUnlock(m_hLogData);
7733     CloseClipboard();
7734     m_locked = 0;
7735 }
7736 #endif /* WIN_EX */
7737 #endif /* CAN_CUT_AND_PASTE */
7738 
7739 #if defined(WIN_EX)
7740 
7741 #ifndef WSABASEERR
7742 #define WSABASEERR 10000
7743 #endif
7744 
7745 #ifdef ENABLE_IPV6
7746 #define WSOCK_NAME  "ws2_32"
7747 #else
7748 #define WSOCK_NAME  "wsock32"
7749 #endif
7750 
7751 /*
7752  * Description: the windows32 version of perror()
7753  *
7754  * Returns:  a pointer to a static error
7755  *
7756  * Notes/Dependencies:  I got this from
7757  *	comp.os.ms-windows.programmer.win32
7758  */
w32_strerror(DWORD ercode)7759 char *w32_strerror(DWORD ercode)
7760 {
7761 /*  __declspec(thread) necessary if you will use multiple threads */
7762 #ifdef __CYGWIN__
7763     static char msg_buff[256];
7764 
7765 #else
7766     __declspec(thread) static char msg_buff[256];
7767 #endif
7768     HMODULE hModule;
7769     int i, msg_type;
7770     unsigned char *p, *q, tmp_buff[256];
7771     DWORD rc;
7772 
7773     hModule = NULL;
7774     msg_type = FORMAT_MESSAGE_FROM_SYSTEM;
7775     /* Fill message buffer with a default message in
7776      * case FormatMessage fails
7777      */
7778     wsprintf(msg_buff, "Error %ld", ercode);
7779 
7780     /*
7781      * Special code for winsock error handling.
7782      */
7783     if (ercode > WSABASEERR) {
7784 	hModule = GetModuleHandle(WSOCK_NAME);
7785 	if (hModule)
7786 	    msg_type = FORMAT_MESSAGE_FROM_HMODULE;
7787     }
7788     /*
7789      * message handling. If not found in module, retry from system.
7790      */
7791     rc = FormatMessage(msg_type, hModule, ercode, LANG_NEUTRAL,
7792 		       msg_buff, sizeof(msg_buff), NULL);
7793 
7794     if (rc == 0 && msg_type == FORMAT_MESSAGE_FROM_HMODULE) {
7795 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ercode,
7796 		      LANG_NEUTRAL, msg_buff, sizeof(msg_buff), NULL);
7797     }
7798 
7799     strcpy((char *) tmp_buff, msg_buff);
7800     p = q = tmp_buff;
7801     i = 0;
7802     while (*p) {
7803 	if (!(*p == '\n' || *p == '\r'))
7804 	    msg_buff[i++] = *p;
7805 	p++;
7806     }
7807     msg_buff[i] = '\0';
7808 
7809     return msg_buff;
7810 }
7811 
7812 #endif
7813 
7814 #if defined(SYSLOG_REQUESTED_URLS)
7815 /*
7816  * syslog() interface
7817  */
LYOpenlog(const char * banner)7818 void LYOpenlog(const char *banner)
7819 {
7820     if (syslog_requested_urls) {
7821 	CTRACE((tfp, "LYOpenlog(%s)\n", NONNULL(banner)));
7822 #if defined(DJGPP)
7823 	openlog("lynx", LOG_PID | LOG_NDELAY, LOG_LOCAL5);
7824 #else
7825 	openlog("lynx", LOG_PID, LOG_LOCAL5);
7826 #endif
7827 
7828 	if (banner) {
7829 	    syslog(LOG_INFO, "Session start:%s", banner);
7830 	} else {
7831 	    syslog(LOG_INFO, "Session start");
7832 	}
7833     }
7834 }
7835 
looks_like_password(char * first,char * last)7836 static BOOLEAN looks_like_password(char *first,
7837 				   char *last)
7838 {
7839     BOOLEAN result = FALSE;
7840 
7841     while (first <= last) {
7842 	if (*first == '/'
7843 	    || *first == ':') {
7844 	    result = FALSE;
7845 	    break;
7846 	}
7847 	result = TRUE;
7848 	first++;
7849     }
7850     return result;
7851 }
7852 
LYSyslog(char * arg)7853 void LYSyslog(char *arg)
7854 {
7855     char *colon1;
7856     char *colon2;
7857     char *atsign;
7858 
7859     if (syslog_requested_urls) {
7860 
7861 	CTRACE((tfp, "LYSyslog %s\n", arg));
7862 
7863 	if (is_url(arg)) {	/* proto://user:password@host/path:port */
7864 	    /*      ^this colon                 */
7865 	    if ((colon1 = strchr(arg, ':')) != 0
7866 		&& !StrNCmp(colon1, "://", 3)
7867 		&& (colon2 = strchr(colon1 + 3, ':')) != 0
7868 		&& (atsign = strchr(colon1, '@')) != 0
7869 		&& (colon2 < atsign)
7870 		&& looks_like_password(colon2 + 1, atsign - 1)) {
7871 		char *buf = NULL;
7872 
7873 		StrAllocCopy(buf, arg);
7874 		buf[colon2 - arg + 1] = 0;
7875 		StrAllocCat(buf, "******");
7876 		StrAllocCat(buf, atsign);
7877 		syslog(LOG_INFO | LOG_LOCAL5, "%s", buf);
7878 		CTRACE((tfp, "...alter %s\n", buf));
7879 		FREE(buf);
7880 		return;
7881 	    }
7882 	}
7883 	syslog(LOG_INFO | LOG_LOCAL5, "%s", NONNULL(arg));
7884     }
7885 }
7886 
LYCloselog(void)7887 void LYCloselog(void)
7888 {
7889     if (syslog_requested_urls) {
7890 	syslog(LOG_INFO, "Session over");
7891 	closelog();
7892     }
7893 }
7894 
7895 #endif /* SYSLOG_REQUESTED_URLS */
7896 
7897 #if defined(WANT_LYmsec_delay) || defined(USE_MOUSE)
7898 /*
7899  * Sleep for a number of milli-sec.
7900  */
LYmsec_delay(unsigned msec)7901 void LYmsec_delay(unsigned msec)
7902 {
7903 #if defined(_WINDOWS)
7904     Sleep(msec);
7905 
7906 #elif defined(HAVE_NAPMS)
7907     napms((int) msec);
7908 
7909 #elif defined(DJGPP) || defined(HAVE_USLEEP)
7910     usleep(1000 * msec);
7911 
7912 #else
7913     struct timeval tv;
7914     unsigned long usec = 1000UL * msec;
7915 
7916     tv.tv_sec = usec / 1000000UL;
7917     tv.tv_usec = usec % 1000000UL;
7918     select(0, NULL, NULL, NULL, &tv);
7919 #endif
7920 }
7921 #endif /* WANT_LYmsec_delay || USE_MOUSE */
7922 
7923 #if defined(WIN_EX) || defined(__CYGWIN__)	/* 2000/03/07 (Tue) 17:17:46 */
7924 
7925 #define IS_SEP(p)	((p == '\\') || (p == '/') || (p == ':'))
7926 
7927 static char *black_list[] =
7928 {
7929     "con",
7930     "prn",
7931     "clock$",
7932     "config$",
7933     NULL
7934 };
7935 
is_device(char * fname)7936 static int is_device(char *fname)
7937 {
7938     HANDLE fileHandle;
7939     DWORD val;
7940     int i;
7941 
7942     i = 0;
7943     while (black_list[i] != NULL) {
7944 	if (stricmp(fname, black_list[i]) == 0) {
7945 	    return 1;		/* device file */
7946 	}
7947 	i++;
7948     }
7949 
7950     fileHandle = CreateFile(fname, 0, 0, 0, OPEN_EXISTING, 0, 0);
7951 
7952     if (fileHandle == INVALID_HANDLE_VALUE) {
7953 	return 0;		/* normal file */
7954     } else {
7955 	val = GetFileType(fileHandle);
7956 	switch (val) {
7957 	case 1:
7958 	    val = 0;
7959 	    break;
7960 	case 2:
7961 	    val = 1;		/* device file */
7962 	    break;
7963 	default:
7964 	    val = 0;
7965 	    break;
7966 	}
7967 
7968 	CloseHandle(fileHandle);
7969     }
7970     return val;
7971 }
7972 
7973 static char *device_list[] =
7974 {
7975     "con",
7976     "nul",
7977     "aux",
7978     "prn",
7979     NULL
7980 };
7981 
unsafe_filename(const char * fname)7982 int unsafe_filename(const char *fname)
7983 {
7984     int i, len, sum;
7985     char *cp;
7986     char *save;
7987 
7988     i = 0;
7989     while (device_list[i] != NULL) {
7990 	if (stricmp(fname, device_list[i]) == 0) {
7991 	    return 0;		/* device file (open OK) */
7992 	}
7993 	i++;
7994     }
7995 
7996     save = cp = strdup(fname);
7997 
7998     while (*cp) {
7999 	if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp)))
8000 	    cp += 2;		/* KANJI skip */
8001 	if (IS_SEP(*cp)) {
8002 	    *cp = '\0';
8003 	}
8004 	cp++;
8005     }
8006 
8007     sum = 0;
8008     cp = save;
8009     len = strlen(fname);
8010     while (cp < (save + len)) {
8011 	if (*cp == '\0') {
8012 	    cp++;
8013 	} else {
8014 	    char *q;
8015 
8016 	    q = strchr(cp, '.');
8017 	    if (q)
8018 		*q = '\0';
8019 	    if (is_device(cp)) {
8020 		sum++;
8021 		break;
8022 	    }
8023 	    if (q)
8024 		cp = q + 1;
8025 	    while (*cp)
8026 		cp++;
8027 	}
8028     }
8029     free(save);
8030 
8031     return (sum != 0);
8032 }
8033 
safe_fopen(const char * fname,const char * mode)8034 FILE *safe_fopen(const char *fname, const char *mode)
8035 {
8036     if (unsafe_filename(fname)) {
8037 	return (FILE *) NULL;
8038     } else {
8039 	return fopen(fname, mode);
8040     }
8041 }
8042 
8043 #endif /* defined(WIN_EX) || defined(__CYGWIN__) */
8044