1 /*
2  * $LynxId: LYBookmark.c,v 1.78 2018/03/18 19:27:30 tom Exp $
3  */
4 #include <HTUtils.h>
5 #include <HTAlert.h>
6 #include <HTFile.h>
7 #include <LYUtils.h>
8 #include <LYStrings.h>
9 #include <LYBookmark.h>
10 #include <LYGlobalDefs.h>
11 #include <LYClean.h>
12 #include <LYKeymap.h>
13 #include <LYCharUtils.h>	/* need for META charset */
14 #include <UCAux.h>
15 #include <LYCharSets.h>		/* need for LYHaveCJKCharacterSet */
16 #include <LYCurses.h>
17 #include <GridText.h>
18 #include <HTCJK.h>
19 
20 #ifdef VMS
21 #include <nam.h>
22 #endif /* VMS */
23 
24 #include <LYLeaks.h>
25 
26 char *MBM_A_subbookmark[MBM_V_MAXFILES + 1];
27 char *MBM_A_subdescript[MBM_V_MAXFILES + 1];
28 
29 static BOOLEAN is_mosaic_hotlist = FALSE;
30 static const char *convert_mosaic_bookmark_file(const char *filename_buffer);
31 
LYindex2MBM(int n)32 int LYindex2MBM(int n)
33 {
34     static char MBMcodes[MBM_V_MAXFILES + 2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
35 
36     return n >= 0 && n <= MBM_V_MAXFILES ? MBMcodes[n] : '?';
37 }
38 
LYMBM2index(int ch)39 int LYMBM2index(int ch)
40 {
41     if ((ch = TOUPPER(ch)) > 0) {
42 	const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
43 	const char *result = StrChr(letters, ch);
44 
45 	if (result != 0
46 	    && (result - letters) <= MBM_V_MAXFILES)
47 	    return (int) (result - letters);
48     }
49     return -1;
50 }
51 
show_bookmark_not_defined(void)52 static void show_bookmark_not_defined(void)
53 {
54     char *string_buffer = 0;
55 
56     HTSprintf0(&string_buffer,
57 	       BOOKMARK_FILE_NOT_DEFINED,
58 	       key_for_func(LYK_OPTIONS));
59     LYMBM_statusline(string_buffer);
60     FREE(string_buffer);
61 }
62 
63 /*
64  * Tries to open a bookmark file for reading, which may be the default, or
65  * based on offering the user a choice from the MBM_A_subbookmark[] array.  If
66  * successful the file is closed, and the filename in system path specs is
67  * returned, the URL is allocated into *URL, and the MBM_A_subbookmark[]
68  * filepath is allocated into the BookmarkPage global.  Returns a zero-length
69  * pointer to flag a cancel, or a space to flag an undefined selection, without
70  * allocating into *URL or BookmarkPage.  Returns NULL with allocating into
71  * BookmarkPage but not *URL is the selection is valid but the file doesn't yet
72  * exist.  - FM
73  */
get_bookmark_filename(char ** URL)74 const char *get_bookmark_filename(char **URL)
75 {
76     static char filename_buffer[LY_MAXPATH];
77     char *string_buffer = 0;
78     FILE *fp;
79     int MBM_tmp;
80 
81     /*
82      * Multi_Bookmarks support.  - FMG & FM
83      * Let user select a bookmark file.
84      */
85     MBM_tmp = select_multi_bookmarks();
86     if (MBM_tmp == -2)
87 	/*
88 	 * Zero-length pointer flags a cancel.  - FM
89 	 */
90 	return ("");
91     if (MBM_tmp == -1) {
92 	show_bookmark_not_defined();
93 	/*
94 	 * Space flags an undefined selection.  - FMG
95 	 */
96 	return (" ");
97     } else {
98 	/*
99 	 * Save the filepath as a global.  The system path will be loaded into
100 	 * to the (static) filename_buffer as the return value, the URL will be
101 	 * allocated into *URL, and we also need the filepath available to
102 	 * calling functions.  This is all pitifully non-reentrant, a la the
103 	 * original Lynx, and should be redesigned someday.  - FM
104 	 */
105 	StrAllocCopy(BookmarkPage, MBM_A_subbookmark[MBM_tmp]);
106     }
107 
108     /*
109      * Seek it in the home path.  - FM
110      */
111     LYAddPathToHome(filename_buffer,
112 		    sizeof(filename_buffer),
113 		    BookmarkPage);
114     CTRACE((tfp, "\nget_bookmark_filename: SEEKING %s\n   AS %s\n\n",
115 	    BookmarkPage, filename_buffer));
116     if ((fp = fopen(filename_buffer, TXT_R)) != NULL) {
117 	/*
118 	 * We now have the file open.
119 	 * Check if it is a mosaic hotlist.
120 	 */
121 	if (LYSafeGets(&string_buffer, fp) != 0
122 	    && *LYTrimNewline(string_buffer) != '\0'
123 	    && !StrNCmp(string_buffer, "ncsa-xmosaic-hotlist-format-1", 29)) {
124 	    const char *newname;
125 
126 	    /*
127 	     * It is a mosaic hotlist file.
128 	     */
129 	    is_mosaic_hotlist = TRUE;
130 	    newname = convert_mosaic_bookmark_file(filename_buffer);
131 	    LYLocalFileToURL(URL, newname);
132 	} else {
133 	    is_mosaic_hotlist = FALSE;
134 	    LYLocalFileToURL(URL, filename_buffer);
135 	}
136 	FREE(string_buffer);
137 	LYCloseInput(fp);
138 
139 	return (filename_buffer);	/* bookmark file exists */
140     }
141     return (NULL);
142 
143 }				/* big end */
144 
145 /*
146  * Converts a Mosaic hotlist file into an HTML file for handling as a Lynx
147  * bookmark file.  - FM
148  */
convert_mosaic_bookmark_file(const char * filename_buffer)149 static const char *convert_mosaic_bookmark_file(const char *filename_buffer)
150 {
151     static char newfile[LY_MAXPATH];
152     FILE *fp, *nfp;
153     char *buf = NULL;
154     int line = -2;
155 
156     (void) LYRemoveTemp(newfile);
157     if ((nfp = LYOpenTemp(newfile, HTML_SUFFIX, "w")) == NULL) {
158 	LYMBM_statusline(NO_TEMP_FOR_HOTLIST);
159 	LYSleepAlert();
160 	return ("");
161     }
162 
163     if ((fp = fopen(filename_buffer, TXT_R)) == NULL)
164 	return ("");		/* should always open */
165 
166     fprintf(nfp, "<head>\n<title>%s</title>\n</head>\n", MOSAIC_BOOKMARK_TITLE);
167     fprintf(nfp, "%s\n\n<p>\n<ol>\n", gettext("\
168      This file is an HTML representation of the X Mosaic hotlist file.\n\
169      Outdated or invalid links may be removed by using the\n\
170      remove bookmark command, it is usually the 'R' key but may have\n\
171      been remapped by you or your system administrator."));
172 
173     while ((LYSafeGets(&buf, fp)) != NULL) {
174 	if (line >= 0) {
175 	    LYTrimNewline(buf);
176 	    if ((line % 2) == 0) {	/* even lines */
177 		if (*buf != '\0') {
178 		    strtok(buf, " ");	/* kill everything after the space */
179 		    fprintf(nfp, "<LI><a href=\"%s\">", buf);	/* the URL */
180 		}
181 	    } else {		/* odd lines */
182 		fprintf(nfp, "%s</a>\n", buf);	/* the title */
183 	    }
184 	}
185 	/* else - ignore the line (this gets rid of first two lines) */
186 	line++;
187     }
188     LYCloseTempFP(nfp);
189     LYCloseInput(fp);
190     return (newfile);
191 }
192 
193 static BOOLEAN havevisible(const char *Title);
194 static BOOLEAN have8bit(const char *Title);
195 static char *title_convert8bit(const char *Title);
196 
197 /*
198  * Adds a link to a bookmark file, creating the file if it doesn't already
199  * exist, and making sure that no_cache is set for a pre-existing, cached file,
200  * so that the change will be evident on return to to that file.  - FM
201  */
save_bookmark_link(const char * address,const char * title)202 void save_bookmark_link(const char *address,
203 			const char *title)
204 {
205     FILE *fp;
206     BOOLEAN first_time = FALSE;
207     const char *filename;
208     char *bookmark_URL = NULL;
209     char filename_buffer[LY_MAXPATH];
210     char *Address = NULL;
211     char *Title = NULL;
212     int i, c;
213     bstring *string_data = NULL;
214     bstring *tmp_data = NULL;
215     DocAddress WWWDoc;
216     HTParentAnchor *tmpanchor;
217     HText *text;
218 
219     /*
220      * Make sure we were passed something to save.  - FM
221      */
222     if (isEmpty(address)) {
223 	HTAlert(MALFORMED_ADDRESS);
224 	return;
225     }
226 
227     /*
228      * Offer a choice of bookmark files, or get the default.  - FMG
229      */
230     filename = get_bookmark_filename(&bookmark_URL);
231 
232     /*
233      * If filename is NULL, must create a new file.  If filename is a space, an
234      * invalid bookmark file was selected, or if zero-length, the user
235      * cancelled.  Ignore request in both cases.  Otherwise, make a copy before
236      * anything might change the static get_bookmark_filename() buffer.  - FM
237      */
238     if (filename == NULL) {
239 	first_time = TRUE;
240 	filename_buffer[0] = '\0';
241     } else {
242 	if (*filename == '\0' || !strcmp(filename, " ")) {
243 	    FREE(bookmark_URL);
244 	    return;
245 	}
246 	LYStrNCpy(filename_buffer, filename, sizeof(filename_buffer) - 1);
247     }
248 
249     /*
250      * If BookmarkPage is NULL, something went wrong, so ignore the request.  -
251      * FM
252      */
253     if (isEmpty(BookmarkPage)) {
254 	FREE(bookmark_URL);
255 	return;
256     }
257 
258     /*
259      * If the link will be added to the same bookmark file, get confirmation.
260      * - FM
261      */
262     if (LYMultiBookmarks != MBM_OFF) {
263 	const char *url = HTLoadedDocumentURL();
264 	const char *page = ((*BookmarkPage == '.')
265 			    ? (BookmarkPage + 1)
266 			    : BookmarkPage);
267 
268 	if (strstr(url, page) != NULL) {
269 	    LYMBM_statusline(MULTIBOOKMARKS_SELF);
270 	    c = LYgetch_single();
271 	    if (c != 'L') {
272 		FREE(bookmark_URL);
273 		return;
274 	    }
275 	}
276     }
277 
278     /*
279      * Allow user to change the title.  - FM
280      */
281     do {
282 	if (HTCJK == JAPANESE) {
283 	    switch (kanji_code) {
284 	    case EUC:
285 		BStrAlloc(tmp_data, MAX_LINE + 2 * (int) strlen(title));
286 		TO_EUC((const unsigned char *) title, (unsigned char *) tmp_data->str);
287 		break;
288 	    case SJIS:
289 		BStrAlloc(tmp_data, MAX_LINE + (int) strlen(title));
290 		TO_SJIS((const unsigned char *) title, (unsigned char *) tmp_data->str);
291 		break;
292 	    default:
293 		break;
294 	    }
295 	    BStrCopy0(string_data, tmp_data ? tmp_data->str : title);
296 	} else {
297 	    BStrCopy0(string_data, title);
298 	}
299 	LYReduceBlanks(string_data->str);
300 	LYMBM_statusline(TITLE_PROMPT);
301 	LYgetBString(&string_data, FALSE, 0, NORECALL);
302 	if (isBEmpty(string_data)) {
303 	    LYMBM_statusline(CANCELLED);
304 	    LYSleepMsg();
305 	    FREE(bookmark_URL);
306 	    return;
307 	}
308     } while (!havevisible(string_data->str));
309 
310     /*
311      * Create the Title with any left-angle-brackets converted to &lt; entities
312      * and any ampersands converted to &amp; entities.  - FM
313      *
314      * Convert 8-bit letters to &#xUUUU to avoid dependencies from display
315      * character set which may need changing.  Do NOT convert any 8-bit chars
316      * if we have CJK display.  - LP
317      */
318     LYformTitle(&Title, string_data->str);
319     LYEntify(&Title, TRUE);
320     if (UCSaveBookmarksInUnicode &&
321 	have8bit(Title) && (!LYHaveCJKCharacterSet)) {
322 	char *p = title_convert8bit(Title);
323 
324 	if (p != 0) {
325 	    FREE(Title);
326 	    Title = p;
327 	}
328     }
329 
330     /*
331      * Create the bookmark file, if it doesn't exist already, Otherwise, open
332      * the pre-existing bookmark file.  - FM
333      */
334     SetDefaultMode(O_TEXT);
335     if (first_time) {
336 	/*
337 	 * Seek it in the home path.  - FM
338 	 */
339 	LYAddPathToHome(filename_buffer,
340 			sizeof(filename_buffer),
341 			BookmarkPage);
342     }
343     CTRACE((tfp, "\nsave_bookmark_link: SEEKING %s\n   AS %s\n\n",
344 	    BookmarkPage, filename_buffer));
345     if ((fp = fopen(filename_buffer, (first_time ? TXT_W : TXT_A))) == NULL) {
346 	LYMBM_statusline(BOOKMARK_OPEN_FAILED);
347 	LYSleepAlert();
348 	FREE(Title);
349 	FREE(bookmark_URL);
350 	return;
351     }
352 
353     /*
354      * Convert all ampersands in the address to &amp; entities.  - FM
355      */
356     StrAllocCopy(Address, address);
357     LYEntify(&Address, FALSE);
358 
359     /*
360      * If we created a new bookmark file, write the headers.  - FM
361      * Once and forever...
362      */
363     if (first_time) {
364 	fprintf(fp, "<head>\n");
365 #if defined(SH_EX) && !defined(_WINDOWS)	/* 1997/12/11 (Thu) 19:13:40 */
366 	if (HTCJK != JAPANESE)
367 	    LYAddMETAcharsetToFD(fp, -1);
368 	else
369 	    fprintf(fp, "<META %s %s>\n",
370 		    "http-equiv=\"content-type\"",
371 		    "content=\"" STR_HTML ";charset=iso-2022-jp\"");
372 #else
373 	LYAddMETAcharsetToFD(fp, -1);
374 #endif /* !_WINDOWS */
375 	fprintf(fp, "<title>%s</title>\n</head>\n", BOOKMARK_TITLE);
376 #ifdef _WINDOWS
377 	fprintf(fp,
378 		gettext("     You can delete links by the 'R' key<br>\n<ol>\n"));
379 #else
380 	fprintf(fp, "%s<br>\n%s\n\n<!--\n%s\n-->\n\n<p>\n<ol>\n",
381 		gettext("\
382      You can delete links using the remove bookmark command.  It is usually\n\
383      the 'R' key but may have been remapped by you or your system\n\
384      administrator."),
385 		gettext("\
386      This file also may be edited with a standard text editor to delete\n\
387      outdated or invalid links, or to change their order."),
388 		gettext("\
389 Note: if you edit this file manually\n\
390       you should not change the format within the lines\n\
391       or add other HTML markup.\n\
392       Make sure any bookmark link is saved as a single line."));
393 #endif /* _WINDOWS */
394     }
395 
396     /*
397      * Add the bookmark link, in Mosaic hotlist or Lynx format.  - FM
398      */
399     if (is_mosaic_hotlist) {
400 	time_t NowTime = time(NULL);
401 	char *TimeString = (char *) ctime(&NowTime);
402 
403 	/*
404 	 * TimeString has a \n at the end.
405 	 */
406 	fprintf(fp, "%s %s%s\n", Address, TimeString, Title);
407     } else {
408 	fprintf(fp, "<LI><a href=\"%s\">%s</a>\n", Address, Title);
409     }
410     LYCloseOutput(fp);
411 
412     SetDefaultMode(O_BINARY);
413     /*
414      * If this is a cached bookmark file, set nocache for it so we'll see the
415      * new bookmark link when that cache is retrieved.  - FM
416      */
417     if (!first_time && nhist > 0 && bookmark_URL) {
418 	for (i = 0; i < nhist; i++) {
419 	    if (HDOC(i).bookmark &&
420 		!strcmp(HDOC(i).address, bookmark_URL)) {
421 		WWWDoc.address = HDOC(i).address;
422 		WWWDoc.post_data = NULL;
423 		WWWDoc.post_content_type = NULL;
424 		WWWDoc.bookmark = HDOC(i).bookmark;
425 		WWWDoc.isHEAD = FALSE;
426 		WWWDoc.safe = FALSE;
427 		tmpanchor = HTAnchor_findAddress(&WWWDoc);
428 		if ((text = (HText *) HTAnchor_document(tmpanchor)) != NULL) {
429 		    HText_setNoCache(text);
430 		}
431 		break;
432 	    }
433 	}
434     }
435 
436     /*
437      * Clean up and report success.
438      */
439     BStrFree(string_data);
440     BStrFree(tmp_data);
441     FREE(Title);
442     FREE(Address);
443     FREE(bookmark_URL);
444     LYMBM_statusline(OPERATION_DONE);
445     LYSleepMsg();
446 }
447 
448 /*
449  * Remove a link from a bookmark file.  The calling function is expected to
450  * have used get_filename_link(), pass us the link number as cur, the
451  * MBM_A_subbookmark[] string as cur_bookmark_page, and to have set up no_cache
452  * itself.  - FM
453  */
remove_bookmark_link(int cur,char * cur_bookmark_page)454 void remove_bookmark_link(int cur,
455 			  char *cur_bookmark_page)
456 {
457     FILE *fp, *nfp;
458     char *buf = NULL;
459     int n;
460 
461 #ifdef VMS
462     char filename_buffer[NAM$C_MAXRSS + 12];
463     char newfile[NAM$C_MAXRSS + 12];
464 
465 #define keep_tempfile FALSE
466 #else
467     char filename_buffer[LY_MAXPATH];
468     char newfile[LY_MAXPATH];
469     BOOLEAN keep_tempfile = FALSE;
470 
471 #ifdef UNIX
472     struct stat stat_buf;
473     BOOLEAN regular = FALSE;
474 #endif /* UNIX */
475 #endif /* VMS */
476     char homepath[LY_MAXPATH];
477 
478     CTRACE((tfp, "remove_bookmark_link: deleting link number: %d\n", cur));
479 
480     if (!cur_bookmark_page)
481 	return;
482     LYAddPathToHome(filename_buffer,
483 		    sizeof(filename_buffer),
484 		    cur_bookmark_page);
485     CTRACE((tfp, "\nremove_bookmark_link: SEEKING %s\n   AS %s\n\n",
486 	    cur_bookmark_page, filename_buffer));
487     if ((fp = fopen(filename_buffer, TXT_R)) == NULL) {
488 	HTAlert(BOOKMARK_OPEN_FAILED_FOR_DEL);
489 	return;
490     }
491 
492     LYAddPathToHome(homepath, sizeof(homepath), "");
493     if ((nfp = LYOpenScratch(newfile, homepath)) == 0) {
494 	LYCloseInput(fp);
495 	HTAlert(BOOKSCRA_OPEN_FAILED_FOR_DEL);
496 	return;
497     }
498 #ifdef UNIX
499     /*
500      * Explicitly preserve bookmark file mode on Unix.  - DSL
501      */
502     if (stat(filename_buffer, &stat_buf) == 0) {
503 	regular = (BOOLEAN) (S_ISREG(stat_buf.st_mode) && stat_buf.st_nlink == 1);
504 	(void) chmod(newfile, HIDE_CHMOD);
505 	if ((nfp = LYReopenTemp(newfile)) == NULL) {
506 	    (void) LYCloseInput(fp);
507 	    HTAlert(BOOKTEMP_REOPEN_FAIL_FOR_DEL);
508 	    return;
509 	}
510     }
511 #endif /* UNIX */
512 
513     if (is_mosaic_hotlist) {
514 	int del_line = cur * 2;	/* two lines per entry */
515 
516 	n = -3;			/* skip past cookie and name lines */
517 	while (LYSafeGets(&buf, fp) != NULL) {
518 	    n++;
519 	    if (n == del_line || n == del_line + 1)
520 		continue;	/* remove two lines */
521 	    if (fputs(buf, nfp) == EOF)
522 		goto failure;
523 	}
524 
525     } else {
526 	char *cp;
527 	BOOLEAN retain;
528 	int seen;
529 
530 	n = -1;
531 	while (LYSafeGets(&buf, fp) != NULL) {
532 	    int keep_ol = FALSE;
533 
534 	    retain = TRUE;
535 	    seen = 0;
536 	    cp = buf;
537 	    if ((cur == 0) && LYstrstr(cp, "<ol><LI>"))
538 		keep_ol = TRUE;	/* Do not erase, this corrects a bug in an
539 				   older version */
540 	    while (n < cur && (cp = LYstrstr(cp, "<a href="))) {
541 		seen++;
542 		if (++n == cur) {
543 		    if (seen != 1 || !LYstrstr(buf, "</a>") ||
544 			LYstrstr((cp + 1), "<a href=")) {
545 			HTAlert(BOOKMARK_LINK_NOT_ONE_LINE);
546 			goto failure;
547 		    }
548 		    CTRACE((tfp, "remove_bookmark_link: skipping link %d\n", n));
549 		    if (keep_ol)
550 			fprintf(nfp, "<ol>\n");
551 		    retain = FALSE;
552 		}
553 		cp += 8;
554 	    }
555 	    if (retain && fputs(buf, nfp) == EOF)
556 		goto failure;
557 	}
558     }
559 
560     FREE(buf);
561     CTRACE((tfp, "remove_bookmark_link: files: %s %s\n",
562 	    newfile, filename_buffer));
563 
564     LYCloseInput(fp);
565     fp = NULL;
566     if (fflush(nfp) == EOF) {
567 	CTRACE((tfp, "fflush(nfp): %s", LYStrerror(errno)));
568 	goto failure;
569     }
570     LYCloseTempFP(nfp);
571     nfp = NULL;
572 #if defined(DOSPATH) || defined(__EMX__)
573     remove(filename_buffer);
574 #endif /* DOSPATH */
575 
576 #ifdef UNIX
577     /*
578      * By copying onto the bookmark file, rather than renaming it, we can
579      * preserve the original ownership of the file, provided that it is
580      * writable by the current process.
581      *
582      * Changed to copy 1998-04-26 -- gil
583      *
584      * But if the copy fails, for example because the filesystem is full, we
585      * are left with a corrupt bookmark file.  Changed back to use the previous
586      * mechanism [try rename(), then mv for EXDEV], except in usual cases (not
587      * a regular file e.g., symbolic link, or has hard links).  This will let
588      * bookmarks survive a filesystem full condition in the "normal" case
589      * (bookmark is on same filesystem as home directory, is a regular file,
590      * has no additional hard links).
591      *
592      * If we first tried LYCopyFile, and that fails, also fall back to trying
593      * the other stuff.  That gives a chance to recover in case the LYCopyFile
594      * left a corrupt target file.
595      *
596      * If there is an error, and that error may mean that the bookmark file has
597      * been corrupted, don't remove the temporary newfile (which should always
598      * be uncorrupted) in place, it may still be used to recover manually.  If
599      * this applies, produce an additional message to that effect.  The temp
600      * file will still be removed by normal program exit cleanup.  - kw
601      * 1999-11-12
602      */
603     if (!regular) {
604 	if (LYCopyFile(newfile, filename_buffer) == 0) {
605 	    (void) LYRemoveTemp(newfile);
606 	    return;
607 	}
608 	LYSleepAlert();		/* give a chance to see error from cp - kw */
609 	HTUserMsg(BOOKTEMP_COPY_FAIL);
610 	keep_tempfile = TRUE;
611     }
612 #endif /* UNIX */
613 
614     if (rename(newfile, filename_buffer) != -1) {
615 #ifdef MULTI_USER_UNIX
616 	if (regular)
617 	    chmod(filename_buffer, stat_buf.st_mode & 07777);
618 #endif
619 	HTSYS_purge(filename_buffer);
620 	return;
621     } else {
622 #ifndef VMS
623 	/*
624 	 * Rename won't work across file systems.  Check if this is the case
625 	 * and do something appropriate.  Used to be ODD_RENAME
626 	 */
627 #if defined(_WINDOWS) || defined(WIN_EX)
628 #if defined(WIN_EX)
629 	if (GetLastError() == ERROR_NOT_SAME_DEVICE)
630 #else /* !_WIN_EX */
631 	if (errno == ENOTSAM)
632 #endif /* _WIN_EX */
633 	{
634 	    if (rename(newfile, filename_buffer) != 0) {
635 		if (LYCopyFile(newfile, filename_buffer) == 0)
636 		    remove(newfile);
637 	    }
638 	}
639 #else
640 	if (errno == EXDEV) {
641 	    static const char MV_FMT[] = "%s %s %s";
642 	    char *buffer = 0;
643 	    const char *program;
644 
645 	    if ((program = HTGetProgramPath(ppMV)) != NULL) {
646 		HTAddParam(&buffer, MV_FMT, 1, program);
647 		HTAddParam(&buffer, MV_FMT, 2, newfile);
648 		HTAddParam(&buffer, MV_FMT, 3, filename_buffer);
649 		HTEndParam(&buffer, MV_FMT, 3);
650 		if (LYSystem(buffer) == 0) {
651 #ifdef MULTI_USER_UNIX
652 		    if (regular)
653 			chmod(filename_buffer, stat_buf.st_mode & 07777);
654 #endif
655 		    FREE(buffer);
656 		    return;
657 		}
658 	    }
659 	    FREE(buffer);
660 	    keep_tempfile = TRUE;
661 	    goto failure;
662 	}
663 	CTRACE((tfp, "rename(): %s", LYStrerror(errno)));
664 #endif /* _WINDOWS */
665 #endif /* !VMS */
666 
667 #ifdef VMS
668 	HTAlert(ERROR_RENAMING_SCRA);
669 #else
670 	HTAlert(ERROR_RENAMING_TEMP);
671 #endif /* VMS */
672 	if (TRACE)
673 	    perror("renaming the file");
674     }
675 
676   failure:
677     FREE(buf);
678     HTAlert(BOOKMARK_DEL_FAILED);
679     if (nfp)
680 	LYCloseTempFP(nfp);
681     if (fp != NULL)
682 	LYCloseInput(fp);
683     if (keep_tempfile) {
684 	HTUserMsg2(gettext("File may be recoverable from %s during this session"),
685 		   newfile);
686     } else {
687 	(void) LYRemoveTemp(newfile);
688     }
689 }
690 
691 /*
692  * Allows user to select sub-bookmarks files.  - FMG & FM
693  */
select_multi_bookmarks(void)694 int select_multi_bookmarks(void)
695 {
696     int c;
697 
698     /*
699      * If not enabled, pick the "default" (0).
700      */
701     if (LYMultiBookmarks == MBM_OFF || LYHaveSubBookmarks() == FALSE) {
702 	if (MBM_A_subbookmark[0])	/* If it exists! */
703 	    return (0);
704 	else
705 	    return (-1);
706     }
707 
708     /*
709      * For ADVANCED users, we can just mess with the status line to save the 2
710      * redraws of the screen, if LYMBMAdvnced is TRUE.  '=' will still show the
711      * screen and let them do it the "long" way.
712      */
713     if (LYMultiBookmarks == MBM_ADVANCED && user_mode == ADVANCED_MODE) {
714 	LYMBM_statusline(MULTIBOOKMARKS_SELECT);
715       get_advanced_choice:
716 	c = LYgetch();
717 #ifdef VMS
718 	if (HadVMSInterrupt) {
719 	    HadVMSInterrupt = FALSE;
720 	    c = LYCharINTERRUPT2;
721 	}
722 #endif /* VMS */
723 	if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || LYCharIsINTERRUPT_HARD(c)) {
724 	    /*
725 	     * Treat left-arrow, ^G, or ^C as cancel.
726 	     */
727 	    return (-2);
728 	}
729 	if (LYisNonAlnumKeyname(c, LYK_REFRESH)) {
730 	    /*
731 	     * Refresh the screen.
732 	     */
733 	    lynx_force_repaint();
734 	    LYrefresh();
735 	    goto get_advanced_choice;
736 	}
737 	if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) {
738 	    /*
739 	     * Assume default bookmark file on ENTER or right-arrow.
740 	     */
741 	    return (MBM_A_subbookmark[0] ? 0 : -1);
742 	}
743 	switch (c) {
744 	case '=':
745 	    /*
746 	     * Get the choice via the menu.
747 	     */
748 	    return (select_menu_multi_bookmarks());
749 
750 	default:
751 	    /*
752 	     * Convert to an array index, act on it if valid.
753 	     * Otherwise, get another keystroke.
754 	     */
755 	    if ((c = LYMBM2index(c)) < 0) {
756 		goto get_advanced_choice;
757 	    }
758 	}
759 	/*
760 	 * See if we have a bookmark like that.
761 	 */
762 	return (MBM_A_subbookmark[c] ? c : -1);
763     } else {
764 	/*
765 	 * Get the choice via the menu.
766 	 */
767 	return (select_menu_multi_bookmarks());
768     }
769 }
770 
771 /*
772  * Allows user to select sub-bookmarks files.  - FMG & FM
773  */
select_menu_multi_bookmarks(void)774 int select_menu_multi_bookmarks(void)
775 {
776     int c, d, MBM_tmp_count, MBM_allow;
777     int MBM_screens, MBM_from, MBM_to, MBM_current;
778 
779     /*
780      * If not enabled, pick the "default" (0).
781      */
782     if (LYMultiBookmarks == MBM_OFF)
783 	return (0);
784 
785     /*
786      *        Filip M. Gieszczykiewicz (filipg@paranoia.com) & FM
787      *  ---------------------------------------------------
788      * MBM_A_subbookmark[n] - Hold values of the respective "multi_bookmarkn"
789      * in the lynxrc file.
790      *
791      * MBM_A_subdescript[n] - Hold description entries in the lynxrc file.
792      *
793      * Note: MBM_A_subbookmark[0] is defined to be same value as
794      *       "bookmark_file" in the lynxrc file and/or the startup
795      *       "bookmark_page".
796      *
797      * We make the display of bookmarks depend on rows we have available.
798      *
799      * We load BookmarkPage with the valid MBM_A_subbookmark[n] via
800      * get_bookmark_filename().  Otherwise, that function returns a zero-length
801      * string to indicate a cancel, a single space to indicate an invalid
802      * choice, or NULL to indicate an inaccessible file.
803      */
804     MBM_allow = (LYlines - 7);	/* We need 7 for header and footer */
805     /*
806      * Screen big enough?
807      */
808     if (MBM_allow <= 0) {
809 	/*
810 	 * Too small.
811 	 */
812 	HTAlert(MULTIBOOKMARKS_SMALL);
813 	return (-2);
814     }
815 
816     MBM_screens = (MBM_V_MAXFILES / MBM_allow) + 1;	/* int rounds off low. */
817 
818     MBM_current = 1;		/* Gotta start somewhere :-) */
819 
820     for (;;) {
821 	MBM_from = MBM_allow * MBM_current - MBM_allow;
822 	if (MBM_from < 0)
823 	    MBM_from = 0;	/* 0 is default bookmark... */
824 	if (MBM_current != 1)
825 	    MBM_from++;
826 
827 	MBM_to = (MBM_allow * MBM_current);
828 	if (MBM_to > MBM_V_MAXFILES)
829 	    MBM_to = MBM_V_MAXFILES;
830 
831 	/*
832 	 * Display menu of bookmarks.  NOTE that we avoid printw()'s to
833 	 * increase the chances that any non-ASCII or multibyte/CJK characters
834 	 * will be handled properly.  - FM
835 	 */
836 	LYclear();
837 	LYmove(1, 5);
838 	lynx_start_h1_color();
839 	if (MBM_screens > 1) {
840 	    char *shead_buffer = 0;
841 
842 	    HTSprintf0(&shead_buffer,
843 		       MULTIBOOKMARKS_SHEAD_MASK, MBM_current, MBM_screens);
844 	    LYaddstr(shead_buffer);
845 	    FREE(shead_buffer);
846 	} else {
847 	    LYaddstr(MULTIBOOKMARKS_SHEAD);
848 	}
849 
850 	lynx_stop_h1_color();
851 
852 	MBM_tmp_count = 0;
853 	for (c = MBM_from; c <= MBM_to; c++) {
854 	    LYmove(3 + MBM_tmp_count, 5);
855 	    LYaddch((chtype) LYindex2MBM(c));
856 	    LYaddstr(" : ");
857 	    if (MBM_A_subdescript[c])
858 		LYaddstr(MBM_A_subdescript[c]);
859 	    LYmove(3 + MBM_tmp_count, 36);
860 	    LYaddch('(');
861 	    if (MBM_A_subbookmark[c])
862 		LYaddstr(MBM_A_subbookmark[c]);
863 	    LYaddch(')');
864 	    MBM_tmp_count++;
865 	}
866 
867 	/*
868 	 * Don't need to show it if it all fits on one screen!
869 	 */
870 	if (MBM_screens > 1) {
871 	    LYmove(LYlines - 2, 0);
872 	    LYaddstr("'");
873 	    lynx_start_bold();
874 	    LYaddstr("[");
875 	    lynx_stop_bold();
876 	    LYaddstr("' ");
877 	    LYaddstr(PREVIOUS);
878 	    LYaddstr(", '");
879 	    lynx_start_bold();
880 	    LYaddstr("]");
881 	    lynx_stop_bold();
882 	    LYaddstr("' ");
883 	    LYaddstr(NEXT_SCREEN);
884 	}
885 
886 	LYMBM_statusline(MULTIBOOKMARKS_SAVE);
887 
888 	for (;;) {
889 	    c = LYgetch();
890 #ifdef VMS
891 	    if (HadVMSInterrupt) {
892 		HadVMSInterrupt = FALSE;
893 		c = 7;
894 	    }
895 #endif /* VMS */
896 
897 	    if ((d = LYMBM2index(c)) >= 0) {
898 		/*
899 		 * See if we have a bookmark like that.
900 		 */
901 		if (non_empty(MBM_A_subbookmark[d]))
902 		    return (d);
903 
904 		show_bookmark_not_defined();
905 		LYMBM_statusline(MULTIBOOKMARKS_SAVE);
906 	    } else if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) ||
907 		       c == 7 || c == 3) {
908 		/*
909 		 * Treat left-arrow, ^G, or ^C as cancel.
910 		 */
911 		return (-2);
912 	    } else if (LYisNonAlnumKeyname(c, LYK_REFRESH)) {
913 		/*
914 		 * Refresh the screen.
915 		 */
916 		lynx_force_repaint();
917 		LYrefresh();
918 	    } else if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) {
919 		/*
920 		 * Assume default bookmark file on ENTER or right-arrow.
921 		 */
922 		return (MBM_A_subbookmark[0] ? 0 : -1);
923 	    } else if ((c == ']' || LYisNonAlnumKeyname(c, LYK_NEXT_PAGE)) &&
924 		       MBM_screens > 1) {
925 		/*
926 		 * Next range, if available.
927 		 */
928 		if (++MBM_current > MBM_screens)
929 		    MBM_current = 1;
930 		break;
931 	    }
932 
933 	    else if ((c == '[' || LYisNonAlnumKeyname(c, LYK_PREV_PAGE)) &&
934 		     MBM_screens > 1) {
935 		/*
936 		 * Previous range, if available.
937 		 */
938 		if (--MBM_current <= 0)
939 		    MBM_current = MBM_screens;
940 		break;
941 	    }
942 	}
943     }
944 }
945 
946 /*
947  * This function returns TRUE if we have sub-bookmarks defined.  Otherwise
948  * (i.e., only the default bookmark file is defined), it returns FALSE.  - FM
949  */
LYHaveSubBookmarks(void)950 BOOLEAN LYHaveSubBookmarks(void)
951 {
952     int i;
953 
954     for (i = 1; i < MBM_V_MAXFILES; i++) {
955 	if (non_empty(MBM_A_subbookmark[i]))
956 	    return (TRUE);
957     }
958 
959     return (FALSE);
960 }
961 
962 /*
963  * This function passes a string to _statusline(), making sure it is at the
964  * bottom of the screen if LYMultiBookmarks is not MBM_OFF, otherwise, letting
965  * it go to the normal statusline position based on the current user mode.  We
966  * want to use _statusline() so that any multibyte/CJK characters in the string
967  * will be handled properly.  - FM
968  */
LYMBM_statusline(const char * text)969 void LYMBM_statusline(const char *text)
970 {
971     if (LYMultiBookmarks != MBM_OFF && user_mode == NOVICE_MODE) {
972 	LYStatusLine = (LYlines - 1);
973 	_statusline(text);
974 	LYStatusLine = -1;
975     } else {
976 	_statusline(text);
977     }
978 }
979 
980 /*
981  * Check whether we have any visible (non-blank) chars.
982  */
havevisible(const char * Title)983 static BOOLEAN havevisible(const char *Title)
984 {
985     BOOLEAN result = FALSE;
986     const char *p = Title;
987     unsigned char c;
988     long unicode;
989 
990     for (; *p; p++) {
991 	c = UCH(TOASCII(*p));
992 	if (c > 32 && c < 127) {
993 	    result = TRUE;
994 	    break;
995 	}
996 	if (c <= 32 || c == 127)
997 	    continue;
998 	if (LYHaveCJKCharacterSet || !UCCanUniTranslateFrom(current_char_set)) {
999 	    result = TRUE;
1000 	    break;
1001 	}
1002 	unicode = UCTransToUni(*p, current_char_set);
1003 	if (unicode == ucNeedMore)
1004 	    continue;
1005 	if (unicode > 32 && unicode < 127) {
1006 	    result = TRUE;
1007 	    break;
1008 	}
1009 	if (unicode <= 32 || unicode == 0xa0 || unicode == 0xad)
1010 	    continue;
1011 	if (unicode < 0x2000 || unicode >= 0x200f) {
1012 	    result = TRUE;
1013 	    break;
1014 	}
1015     }
1016     return (result);
1017 }
1018 
1019 /*
1020  * Check whether string have 8 bit chars.
1021  */
have8bit(const char * Title)1022 static BOOLEAN have8bit(const char *Title)
1023 {
1024     const char *p = Title;
1025 
1026     for (; *p; p++) {
1027 	if (UCH(*p) > 127)
1028 	    return (TRUE);
1029     }
1030     return (FALSE);		/* if we came here */
1031 }
1032 
1033 /*
1034  * Ok, title have 8-bit characters and they are in display charset.  Bookmarks
1035  * is a permanent file.  To avoid dependencies from display character set which
1036  * may be changed with time we store 8-bit characters as numeric character
1037  * reference (NCR), so where the character encoded as unicode number in form of
1038  * &#xUUUU;
1039  *
1040  * To make bookmarks more readable for human (&#xUUUU certainly not) we add a
1041  * comment with '7-bit approximation' from the converted string.  This is a
1042  * valid HTML and bookmarks code.
1043  *
1044  * We do not want use META charset tag in bookmarks file:  it will never be
1045  * changed later :-(
1046  *
1047  * NCR's translation is part of I18N and HTML4.0 supported starting with Lynx
1048  * 2.7.2, Netscape 4.0 and MSIE 4.0.  Older versions fail.
1049  */
title_convert8bit(const char * Title)1050 static char *title_convert8bit(const char *Title)
1051 {
1052     const char *p = Title;
1053     char *p0;
1054     char *q;
1055     char *comment = NULL;
1056     char *ncr = NULL;
1057     char *buf = NULL;
1058     int charset_in = current_char_set;
1059     int charset_out = UCGetLYhndl_byMIME("us-ascii");
1060 
1061     for (; *p; p++) {
1062 	char temp[2];
1063 
1064 	LYStrNCpy(temp, p, sizeof(temp) - 1);
1065 	if (UCH(*temp) <= 127) {
1066 	    StrAllocCat(comment, temp);
1067 	    StrAllocCat(ncr, temp);
1068 	} else if (charset_out >= 0) {
1069 	    long unicode;
1070 	    char replace_buf[32];
1071 
1072 	    if (UCTransCharStr(replace_buf, (int) sizeof(replace_buf), *temp,
1073 			       charset_in, charset_out, YES) > 0)
1074 		StrAllocCat(comment, replace_buf);
1075 
1076 	    unicode = UCTransToUni(*temp, charset_in);
1077 
1078 	    StrAllocCat(ncr, "&#");
1079 	    sprintf(replace_buf, "%ld", unicode);
1080 	    StrAllocCat(ncr, replace_buf);
1081 	    StrAllocCat(ncr, ";");
1082 	}
1083     }
1084 
1085     if (comment != NULL) {
1086 	/*
1087 	 * Cleanup comment, collapse multiple dashes into one dash, skip '>'.
1088 	 */
1089 	for (q = p0 = comment; *p0; p0++) {
1090 	    if (UCH(TOASCII(*p0)) >= 32 &&
1091 		*p0 != '>' &&
1092 		(q == comment || *p0 != '-' || *(q - 1) != '-')) {
1093 		*q++ = *p0;
1094 	    }
1095 	}
1096 	*q = '\0';
1097 
1098 	/*
1099 	 * valid bookmark should be a single line (no linebreaks!).
1100 	 */
1101 	StrAllocCat(buf, "<!-- ");
1102 	StrAllocCat(buf, comment);
1103 	StrAllocCat(buf, " -->");
1104 	StrAllocCat(buf, ncr);
1105 
1106 	FREE(comment);
1107     }
1108     FREE(ncr);
1109     return (buf);
1110 }
1111 
1112 /*
1113  * Since this is the "Default Bookmark File", we save it as a global, and as
1114  * the first MBM_A_subbookmark entry.
1115  */
set_default_bookmark_page(char * value)1116 void set_default_bookmark_page(char *value)
1117 {
1118     if (value != 0) {
1119 	if (bookmark_page == NULL
1120 	    || strcmp(bookmark_page, value)) {
1121 	    StrAllocCopy(bookmark_page, value);
1122 	}
1123 	StrAllocCopy(BookmarkPage, bookmark_page);
1124 	StrAllocCopy(MBM_A_subbookmark[0], bookmark_page);
1125 	StrAllocCopy(MBM_A_subdescript[0], MULTIBOOKMARKS_DEFAULT);
1126     }
1127 }
1128