1 /*
2  * $LynxId: LYBookmark.c,v 1.80 2019/01/02 21:14:08 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 unsigned LYindex2MBM(int n)
33 {
34     static char MBMcodes[MBM_V_MAXFILES + 2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
35 
36     return n >= 0 && n <= MBM_V_MAXFILES ? UCH(MBMcodes[n]) : UCH('?');
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 	    BStrFree(tmp_data);
307 	    return;
308 	}
309     } while (!havevisible(string_data->str));
310 
311     /*
312      * Create the Title with any left-angle-brackets converted to &lt; entities
313      * and any ampersands converted to &amp; entities.  - FM
314      *
315      * Convert 8-bit letters to &#xUUUU to avoid dependencies from display
316      * character set which may need changing.  Do NOT convert any 8-bit chars
317      * if we have CJK display.  - LP
318      */
319     LYformTitle(&Title, string_data->str);
320     LYEntify(&Title, TRUE);
321     if (UCSaveBookmarksInUnicode &&
322 	have8bit(Title) && (!LYHaveCJKCharacterSet)) {
323 	char *p = title_convert8bit(Title);
324 
325 	if (p != 0) {
326 	    FREE(Title);
327 	    Title = p;
328 	}
329     }
330 
331     /*
332      * Create the bookmark file, if it doesn't exist already, Otherwise, open
333      * the pre-existing bookmark file.  - FM
334      */
335     SetDefaultMode(O_TEXT);
336     if (first_time) {
337 	/*
338 	 * Seek it in the home path.  - FM
339 	 */
340 	LYAddPathToHome(filename_buffer,
341 			sizeof(filename_buffer),
342 			BookmarkPage);
343     }
344     CTRACE((tfp, "\nsave_bookmark_link: SEEKING %s\n   AS %s\n\n",
345 	    BookmarkPage, filename_buffer));
346     if ((fp = fopen(filename_buffer, (first_time ? TXT_W : TXT_A))) == NULL) {
347 	LYMBM_statusline(BOOKMARK_OPEN_FAILED);
348 	LYSleepAlert();
349 	FREE(Title);
350 	FREE(bookmark_URL);
351 	BStrFree(tmp_data);
352 	return;
353     }
354 
355     /*
356      * Convert all ampersands in the address to &amp; entities.  - FM
357      */
358     StrAllocCopy(Address, address);
359     LYEntify(&Address, FALSE);
360 
361     /*
362      * If we created a new bookmark file, write the headers.  - FM
363      * Once and forever...
364      */
365     if (first_time) {
366 	fprintf(fp, "<head>\n");
367 #if defined(SH_EX) && !defined(_WINDOWS)	/* 1997/12/11 (Thu) 19:13:40 */
368 	if (HTCJK != JAPANESE)
369 	    LYAddMETAcharsetToFD(fp, -1);
370 	else
371 	    fprintf(fp, "<META %s %s>\n",
372 		    "http-equiv=\"content-type\"",
373 		    "content=\"" STR_HTML ";charset=iso-2022-jp\"");
374 #else
375 	LYAddMETAcharsetToFD(fp, -1);
376 #endif /* !_WINDOWS */
377 	fprintf(fp, "<title>%s</title>\n</head>\n", BOOKMARK_TITLE);
378 #ifdef _WINDOWS
379 	fprintf(fp,
380 		gettext("     You can delete links by the 'R' key<br>\n<ol>\n"));
381 #else
382 	fprintf(fp, "%s<br>\n%s\n\n<!--\n%s\n-->\n\n<p>\n<ol>\n",
383 		gettext("\
384      You can delete links using the remove bookmark command.  It is usually\n\
385      the 'R' key but may have been remapped by you or your system\n\
386      administrator."),
387 		gettext("\
388      This file also may be edited with a standard text editor to delete\n\
389      outdated or invalid links, or to change their order."),
390 		gettext("\
391 Note: if you edit this file manually\n\
392       you should not change the format within the lines\n\
393       or add other HTML markup.\n\
394       Make sure any bookmark link is saved as a single line."));
395 #endif /* _WINDOWS */
396     }
397 
398     /*
399      * Add the bookmark link, in Mosaic hotlist or Lynx format.  - FM
400      */
401     if (is_mosaic_hotlist) {
402 	time_t NowTime = time(NULL);
403 	char *TimeString = (char *) ctime(&NowTime);
404 
405 	/*
406 	 * TimeString has a \n at the end.
407 	 */
408 	fprintf(fp, "%s %s%s\n", Address, TimeString, Title);
409     } else {
410 	fprintf(fp, "<LI><a href=\"%s\">%s</a>\n", Address, Title);
411     }
412     LYCloseOutput(fp);
413 
414     SetDefaultMode(O_BINARY);
415     /*
416      * If this is a cached bookmark file, set nocache for it so we'll see the
417      * new bookmark link when that cache is retrieved.  - FM
418      */
419     if (!first_time && nhist > 0 && bookmark_URL) {
420 	for (i = 0; i < nhist; i++) {
421 	    if (HDOC(i).bookmark &&
422 		!strcmp(HDOC(i).address, bookmark_URL)) {
423 		WWWDoc.address = HDOC(i).address;
424 		WWWDoc.post_data = NULL;
425 		WWWDoc.post_content_type = NULL;
426 		WWWDoc.bookmark = HDOC(i).bookmark;
427 		WWWDoc.isHEAD = FALSE;
428 		WWWDoc.safe = FALSE;
429 		tmpanchor = HTAnchor_findAddress(&WWWDoc);
430 		if ((text = (HText *) HTAnchor_document(tmpanchor)) != NULL) {
431 		    HText_setNoCache(text);
432 		}
433 		break;
434 	    }
435 	}
436     }
437 
438     /*
439      * Clean up and report success.
440      */
441     BStrFree(string_data);
442     BStrFree(tmp_data);
443     FREE(Title);
444     FREE(Address);
445     FREE(bookmark_URL);
446     LYMBM_statusline(OPERATION_DONE);
447     LYSleepMsg();
448 }
449 
450 /*
451  * Remove a link from a bookmark file.  The calling function is expected to
452  * have used get_filename_link(), pass us the link number as cur, the
453  * MBM_A_subbookmark[] string as cur_bookmark_page, and to have set up no_cache
454  * itself.  - FM
455  */
remove_bookmark_link(int cur,char * cur_bookmark_page)456 void remove_bookmark_link(int cur,
457 			  char *cur_bookmark_page)
458 {
459     FILE *fp, *nfp;
460     char *buf = NULL;
461     int n;
462 
463 #ifdef VMS
464     char filename_buffer[NAM$C_MAXRSS + 12];
465     char newfile[NAM$C_MAXRSS + 12];
466 
467 #define keep_tempfile FALSE
468 #else
469     char filename_buffer[LY_MAXPATH];
470     char newfile[LY_MAXPATH];
471     BOOLEAN keep_tempfile = FALSE;
472 
473 #ifdef UNIX
474     struct stat stat_buf;
475     BOOLEAN regular = FALSE;
476 #endif /* UNIX */
477 #endif /* VMS */
478     char homepath[LY_MAXPATH];
479 
480     CTRACE((tfp, "remove_bookmark_link: deleting link number: %d\n", cur));
481 
482     if (!cur_bookmark_page)
483 	return;
484     LYAddPathToHome(filename_buffer,
485 		    sizeof(filename_buffer),
486 		    cur_bookmark_page);
487     CTRACE((tfp, "\nremove_bookmark_link: SEEKING %s\n   AS %s\n\n",
488 	    cur_bookmark_page, filename_buffer));
489     if ((fp = fopen(filename_buffer, TXT_R)) == NULL) {
490 	HTAlert(BOOKMARK_OPEN_FAILED_FOR_DEL);
491 	return;
492     }
493 
494     LYAddPathToHome(homepath, sizeof(homepath), "");
495     if ((nfp = LYOpenScratch(newfile, homepath)) == 0) {
496 	LYCloseInput(fp);
497 	HTAlert(BOOKSCRA_OPEN_FAILED_FOR_DEL);
498 	return;
499     }
500 #ifdef UNIX
501     /*
502      * Explicitly preserve bookmark file mode on Unix.  - DSL
503      */
504     if (stat(filename_buffer, &stat_buf) == 0) {
505 	regular = (BOOLEAN) (S_ISREG(stat_buf.st_mode) && stat_buf.st_nlink == 1);
506 	(void) chmod(newfile, HIDE_CHMOD);
507 	if ((nfp = LYReopenTemp(newfile)) == NULL) {
508 	    (void) LYCloseInput(fp);
509 	    HTAlert(BOOKTEMP_REOPEN_FAIL_FOR_DEL);
510 	    return;
511 	}
512     }
513 #endif /* UNIX */
514 
515     if (is_mosaic_hotlist) {
516 	int del_line = cur * 2;	/* two lines per entry */
517 
518 	n = -3;			/* skip past cookie and name lines */
519 	while (LYSafeGets(&buf, fp) != NULL) {
520 	    n++;
521 	    if (n == del_line || n == del_line + 1)
522 		continue;	/* remove two lines */
523 	    if (fputs(buf, nfp) == EOF)
524 		goto failure;
525 	}
526 
527     } else {
528 	char *cp;
529 	BOOLEAN retain;
530 	int seen;
531 
532 	n = -1;
533 	while (LYSafeGets(&buf, fp) != NULL) {
534 	    int keep_ol = FALSE;
535 
536 	    retain = TRUE;
537 	    seen = 0;
538 	    cp = buf;
539 	    if ((cur == 0) && LYstrstr(cp, "<ol><LI>"))
540 		keep_ol = TRUE;	/* Do not erase, this corrects a bug in an
541 				   older version */
542 	    while (n < cur && (cp = LYstrstr(cp, "<a href="))) {
543 		seen++;
544 		if (++n == cur) {
545 		    if (seen != 1 || !LYstrstr(buf, "</a>") ||
546 			LYstrstr((cp + 1), "<a href=")) {
547 			HTAlert(BOOKMARK_LINK_NOT_ONE_LINE);
548 			goto failure;
549 		    }
550 		    CTRACE((tfp, "remove_bookmark_link: skipping link %d\n", n));
551 		    if (keep_ol)
552 			fprintf(nfp, "<ol>\n");
553 		    retain = FALSE;
554 		}
555 		cp += 8;
556 	    }
557 	    if (retain && fputs(buf, nfp) == EOF)
558 		goto failure;
559 	}
560     }
561 
562     FREE(buf);
563     CTRACE((tfp, "remove_bookmark_link: files: %s %s\n",
564 	    newfile, filename_buffer));
565 
566     LYCloseInput(fp);
567     fp = NULL;
568     if (fflush(nfp) == EOF) {
569 	CTRACE((tfp, "fflush(nfp): %s", LYStrerror(errno)));
570 	goto failure;
571     }
572     LYCloseTempFP(nfp);
573     nfp = NULL;
574 #if defined(DOSPATH) || defined(__EMX__)
575     remove(filename_buffer);
576 #endif /* DOSPATH */
577 
578 #ifdef UNIX
579     /*
580      * By copying onto the bookmark file, rather than renaming it, we can
581      * preserve the original ownership of the file, provided that it is
582      * writable by the current process.
583      *
584      * Changed to copy 1998-04-26 -- gil
585      *
586      * But if the copy fails, for example because the filesystem is full, we
587      * are left with a corrupt bookmark file.  Changed back to use the previous
588      * mechanism [try rename(), then mv for EXDEV], except in usual cases (not
589      * a regular file e.g., symbolic link, or has hard links).  This will let
590      * bookmarks survive a filesystem full condition in the "normal" case
591      * (bookmark is on same filesystem as home directory, is a regular file,
592      * has no additional hard links).
593      *
594      * If we first tried LYCopyFile, and that fails, also fall back to trying
595      * the other stuff.  That gives a chance to recover in case the LYCopyFile
596      * left a corrupt target file.
597      *
598      * If there is an error, and that error may mean that the bookmark file has
599      * been corrupted, don't remove the temporary newfile (which should always
600      * be uncorrupted) in place, it may still be used to recover manually.  If
601      * this applies, produce an additional message to that effect.  The temp
602      * file will still be removed by normal program exit cleanup.  - kw
603      * 1999-11-12
604      */
605     if (!regular) {
606 	if (LYCopyFile(newfile, filename_buffer) == 0) {
607 	    (void) LYRemoveTemp(newfile);
608 	    return;
609 	}
610 	LYSleepAlert();		/* give a chance to see error from cp - kw */
611 	HTUserMsg(BOOKTEMP_COPY_FAIL);
612 	keep_tempfile = TRUE;
613     }
614 #endif /* UNIX */
615 
616     if (rename(newfile, filename_buffer) != -1) {
617 #ifdef MULTI_USER_UNIX
618 	if (regular)
619 	    chmod(filename_buffer, stat_buf.st_mode & 07777);
620 #endif
621 	HTSYS_purge(filename_buffer);
622 	return;
623     } else {
624 #ifndef VMS
625 	/*
626 	 * Rename won't work across file systems.  Check if this is the case
627 	 * and do something appropriate.  Used to be ODD_RENAME
628 	 */
629 #if defined(_WINDOWS) || defined(WIN_EX)
630 #if defined(WIN_EX)
631 	if (GetLastError() == ERROR_NOT_SAME_DEVICE)
632 #else /* !_WIN_EX */
633 	if (errno == ENOTSAM)
634 #endif /* _WIN_EX */
635 	{
636 	    if (rename(newfile, filename_buffer) != 0) {
637 		if (LYCopyFile(newfile, filename_buffer) == 0)
638 		    remove(newfile);
639 	    }
640 	}
641 #else
642 	if (errno == EXDEV) {
643 	    static const char MV_FMT[] = "%s %s %s";
644 	    char *buffer = 0;
645 	    const char *program;
646 
647 	    if ((program = HTGetProgramPath(ppMV)) != NULL) {
648 		HTAddParam(&buffer, MV_FMT, 1, program);
649 		HTAddParam(&buffer, MV_FMT, 2, newfile);
650 		HTAddParam(&buffer, MV_FMT, 3, filename_buffer);
651 		HTEndParam(&buffer, MV_FMT, 3);
652 		if (LYSystem(buffer) == 0) {
653 #ifdef MULTI_USER_UNIX
654 		    if (regular)
655 			chmod(filename_buffer, stat_buf.st_mode & 07777);
656 #endif
657 		    FREE(buffer);
658 		    return;
659 		}
660 	    }
661 	    FREE(buffer);
662 	    keep_tempfile = TRUE;
663 	    goto failure;
664 	}
665 	CTRACE((tfp, "rename(): %s", LYStrerror(errno)));
666 #endif /* _WINDOWS */
667 #endif /* !VMS */
668 
669 #ifdef VMS
670 	HTAlert(ERROR_RENAMING_SCRA);
671 #else
672 	HTAlert(ERROR_RENAMING_TEMP);
673 #endif /* VMS */
674 	if (TRACE)
675 	    perror("renaming the file");
676     }
677 
678   failure:
679     FREE(buf);
680     HTAlert(BOOKMARK_DEL_FAILED);
681     if (nfp)
682 	LYCloseTempFP(nfp);
683     if (fp != NULL)
684 	LYCloseInput(fp);
685     if (keep_tempfile) {
686 	HTUserMsg2(gettext("File may be recoverable from %s during this session"),
687 		   newfile);
688     } else {
689 	(void) LYRemoveTemp(newfile);
690     }
691 }
692 
693 /*
694  * Allows user to select sub-bookmarks files.  - FMG & FM
695  */
select_multi_bookmarks(void)696 int select_multi_bookmarks(void)
697 {
698     int c;
699 
700     /*
701      * If not enabled, pick the "default" (0).
702      */
703     if (LYMultiBookmarks == MBM_OFF || LYHaveSubBookmarks() == FALSE) {
704 	if (MBM_A_subbookmark[0])	/* If it exists! */
705 	    return (0);
706 	else
707 	    return (-1);
708     }
709 
710     /*
711      * For ADVANCED users, we can just mess with the status line to save the 2
712      * redraws of the screen, if LYMBMAdvnced is TRUE.  '=' will still show the
713      * screen and let them do it the "long" way.
714      */
715     if (LYMultiBookmarks == MBM_ADVANCED && user_mode == ADVANCED_MODE) {
716 	LYMBM_statusline(MULTIBOOKMARKS_SELECT);
717       get_advanced_choice:
718 	c = LYgetch();
719 #ifdef VMS
720 	if (HadVMSInterrupt) {
721 	    HadVMSInterrupt = FALSE;
722 	    c = LYCharINTERRUPT2;
723 	}
724 #endif /* VMS */
725 	if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || LYCharIsINTERRUPT_HARD(c)) {
726 	    /*
727 	     * Treat left-arrow, ^G, or ^C as cancel.
728 	     */
729 	    return (-2);
730 	}
731 	if (LYisNonAlnumKeyname(c, LYK_REFRESH)) {
732 	    /*
733 	     * Refresh the screen.
734 	     */
735 	    lynx_force_repaint();
736 	    LYrefresh();
737 	    goto get_advanced_choice;
738 	}
739 	if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) {
740 	    /*
741 	     * Assume default bookmark file on ENTER or right-arrow.
742 	     */
743 	    return (MBM_A_subbookmark[0] ? 0 : -1);
744 	}
745 	switch (c) {
746 	case '=':
747 	    /*
748 	     * Get the choice via the menu.
749 	     */
750 	    return (select_menu_multi_bookmarks());
751 
752 	default:
753 	    /*
754 	     * Convert to an array index, act on it if valid.
755 	     * Otherwise, get another keystroke.
756 	     */
757 	    if ((c = LYMBM2index(c)) < 0) {
758 		goto get_advanced_choice;
759 	    }
760 	}
761 	/*
762 	 * See if we have a bookmark like that.
763 	 */
764 	return (MBM_A_subbookmark[c] ? c : -1);
765     } else {
766 	/*
767 	 * Get the choice via the menu.
768 	 */
769 	return (select_menu_multi_bookmarks());
770     }
771 }
772 
773 /*
774  * Allows user to select sub-bookmarks files.  - FMG & FM
775  */
select_menu_multi_bookmarks(void)776 int select_menu_multi_bookmarks(void)
777 {
778     int c, d, MBM_tmp_count, MBM_allow;
779     int MBM_screens, MBM_from, MBM_to, MBM_current;
780 
781     /*
782      * If not enabled, pick the "default" (0).
783      */
784     if (LYMultiBookmarks == MBM_OFF)
785 	return (0);
786 
787     /*
788      *        Filip M. Gieszczykiewicz (filipg@paranoia.com) & FM
789      *  ---------------------------------------------------
790      * MBM_A_subbookmark[n] - Hold values of the respective "multi_bookmarkn"
791      * in the lynxrc file.
792      *
793      * MBM_A_subdescript[n] - Hold description entries in the lynxrc file.
794      *
795      * Note: MBM_A_subbookmark[0] is defined to be same value as
796      *       "bookmark_file" in the lynxrc file and/or the startup
797      *       "bookmark_page".
798      *
799      * We make the display of bookmarks depend on rows we have available.
800      *
801      * We load BookmarkPage with the valid MBM_A_subbookmark[n] via
802      * get_bookmark_filename().  Otherwise, that function returns a zero-length
803      * string to indicate a cancel, a single space to indicate an invalid
804      * choice, or NULL to indicate an inaccessible file.
805      */
806     MBM_allow = (LYlines - 7);	/* We need 7 for header and footer */
807     /*
808      * Screen big enough?
809      */
810     if (MBM_allow <= 0) {
811 	/*
812 	 * Too small.
813 	 */
814 	HTAlert(MULTIBOOKMARKS_SMALL);
815 	return (-2);
816     }
817 
818     MBM_screens = (MBM_V_MAXFILES / MBM_allow) + 1;	/* int rounds off low. */
819 
820     MBM_current = 1;		/* Gotta start somewhere :-) */
821 
822     for (;;) {
823 	MBM_from = MBM_allow * MBM_current - MBM_allow;
824 	if (MBM_from < 0)
825 	    MBM_from = 0;	/* 0 is default bookmark... */
826 	if (MBM_current != 1)
827 	    MBM_from++;
828 
829 	MBM_to = (MBM_allow * MBM_current);
830 	if (MBM_to > MBM_V_MAXFILES)
831 	    MBM_to = MBM_V_MAXFILES;
832 
833 	/*
834 	 * Display menu of bookmarks.  NOTE that we avoid printw()'s to
835 	 * increase the chances that any non-ASCII or multibyte/CJK characters
836 	 * will be handled properly.  - FM
837 	 */
838 	LYclear();
839 	LYmove(1, 5);
840 	lynx_start_h1_color();
841 	if (MBM_screens > 1) {
842 	    char *shead_buffer = 0;
843 
844 	    HTSprintf0(&shead_buffer,
845 		       MULTIBOOKMARKS_SHEAD_MASK, MBM_current, MBM_screens);
846 	    LYaddstr(shead_buffer);
847 	    FREE(shead_buffer);
848 	} else {
849 	    LYaddstr(MULTIBOOKMARKS_SHEAD);
850 	}
851 
852 	lynx_stop_h1_color();
853 
854 	MBM_tmp_count = 0;
855 	for (c = MBM_from; c <= MBM_to; c++) {
856 	    LYmove(3 + MBM_tmp_count, 5);
857 	    LYaddch(UCH(LYindex2MBM(c)));
858 	    LYaddstr(" : ");
859 	    if (MBM_A_subdescript[c])
860 		LYaddstr(MBM_A_subdescript[c]);
861 	    LYmove(3 + MBM_tmp_count, 36);
862 	    LYaddch('(');
863 	    if (MBM_A_subbookmark[c])
864 		LYaddstr(MBM_A_subbookmark[c]);
865 	    LYaddch(')');
866 	    MBM_tmp_count++;
867 	}
868 
869 	/*
870 	 * Don't need to show it if it all fits on one screen!
871 	 */
872 	if (MBM_screens > 1) {
873 	    LYmove(LYlines - 2, 0);
874 	    LYaddstr("'");
875 	    lynx_start_bold();
876 	    LYaddstr("[");
877 	    lynx_stop_bold();
878 	    LYaddstr("' ");
879 	    LYaddstr(PREVIOUS);
880 	    LYaddstr(", '");
881 	    lynx_start_bold();
882 	    LYaddstr("]");
883 	    lynx_stop_bold();
884 	    LYaddstr("' ");
885 	    LYaddstr(NEXT_SCREEN);
886 	}
887 
888 	LYMBM_statusline(MULTIBOOKMARKS_SAVE);
889 
890 	for (;;) {
891 	    c = LYgetch();
892 #ifdef VMS
893 	    if (HadVMSInterrupt) {
894 		HadVMSInterrupt = FALSE;
895 		c = 7;
896 	    }
897 #endif /* VMS */
898 
899 	    if ((d = LYMBM2index(c)) >= 0) {
900 		/*
901 		 * See if we have a bookmark like that.
902 		 */
903 		if (non_empty(MBM_A_subbookmark[d]))
904 		    return (d);
905 
906 		show_bookmark_not_defined();
907 		LYMBM_statusline(MULTIBOOKMARKS_SAVE);
908 	    } else if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) ||
909 		       c == 7 || c == 3) {
910 		/*
911 		 * Treat left-arrow, ^G, or ^C as cancel.
912 		 */
913 		return (-2);
914 	    } else if (LYisNonAlnumKeyname(c, LYK_REFRESH)) {
915 		/*
916 		 * Refresh the screen.
917 		 */
918 		lynx_force_repaint();
919 		LYrefresh();
920 	    } else if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) {
921 		/*
922 		 * Assume default bookmark file on ENTER or right-arrow.
923 		 */
924 		return (MBM_A_subbookmark[0] ? 0 : -1);
925 	    } else if ((c == ']' || LYisNonAlnumKeyname(c, LYK_NEXT_PAGE)) &&
926 		       MBM_screens > 1) {
927 		/*
928 		 * Next range, if available.
929 		 */
930 		if (++MBM_current > MBM_screens)
931 		    MBM_current = 1;
932 		break;
933 	    }
934 
935 	    else if ((c == '[' || LYisNonAlnumKeyname(c, LYK_PREV_PAGE)) &&
936 		     MBM_screens > 1) {
937 		/*
938 		 * Previous range, if available.
939 		 */
940 		if (--MBM_current <= 0)
941 		    MBM_current = MBM_screens;
942 		break;
943 	    }
944 	}
945     }
946 }
947 
948 /*
949  * This function returns TRUE if we have sub-bookmarks defined.  Otherwise
950  * (i.e., only the default bookmark file is defined), it returns FALSE.  - FM
951  */
LYHaveSubBookmarks(void)952 BOOLEAN LYHaveSubBookmarks(void)
953 {
954     int i;
955 
956     for (i = 1; i < MBM_V_MAXFILES; i++) {
957 	if (non_empty(MBM_A_subbookmark[i]))
958 	    return (TRUE);
959     }
960 
961     return (FALSE);
962 }
963 
964 /*
965  * This function passes a string to _statusline(), making sure it is at the
966  * bottom of the screen if LYMultiBookmarks is not MBM_OFF, otherwise, letting
967  * it go to the normal statusline position based on the current user mode.  We
968  * want to use _statusline() so that any multibyte/CJK characters in the string
969  * will be handled properly.  - FM
970  */
LYMBM_statusline(const char * text)971 void LYMBM_statusline(const char *text)
972 {
973     if (LYMultiBookmarks != MBM_OFF && user_mode == NOVICE_MODE) {
974 	LYStatusLine = (LYlines - 1);
975 	_statusline(text);
976 	LYStatusLine = -1;
977     } else {
978 	_statusline(text);
979     }
980 }
981 
982 /*
983  * Check whether we have any visible (non-blank) chars.
984  */
havevisible(const char * Title)985 static BOOLEAN havevisible(const char *Title)
986 {
987     BOOLEAN result = FALSE;
988     const char *p = Title;
989     unsigned char c;
990     long unicode;
991 
992     for (; *p; p++) {
993 	c = UCH(TOASCII(*p));
994 	if (c > 32 && c < 127) {
995 	    result = TRUE;
996 	    break;
997 	}
998 	if (c <= 32 || c == 127)
999 	    continue;
1000 	if (LYHaveCJKCharacterSet || !UCCanUniTranslateFrom(current_char_set)) {
1001 	    result = TRUE;
1002 	    break;
1003 	}
1004 	unicode = UCTransToUni(*p, current_char_set);
1005 	if (unicode == ucNeedMore)
1006 	    continue;
1007 	if (unicode > 32 && unicode < 127) {
1008 	    result = TRUE;
1009 	    break;
1010 	}
1011 	if (unicode <= 32 || unicode == 0xa0 || unicode == 0xad)
1012 	    continue;
1013 	if (unicode < 0x2000 || unicode >= 0x200f) {
1014 	    result = TRUE;
1015 	    break;
1016 	}
1017     }
1018     return (result);
1019 }
1020 
1021 /*
1022  * Check whether string have 8 bit chars.
1023  */
have8bit(const char * Title)1024 static BOOLEAN have8bit(const char *Title)
1025 {
1026     const char *p = Title;
1027 
1028     for (; *p; p++) {
1029 	if (UCH(*p) > 127)
1030 	    return (TRUE);
1031     }
1032     return (FALSE);		/* if we came here */
1033 }
1034 
1035 /*
1036  * Ok, title have 8-bit characters and they are in display charset.  Bookmarks
1037  * is a permanent file.  To avoid dependencies from display character set which
1038  * may be changed with time we store 8-bit characters as numeric character
1039  * reference (NCR), so where the character encoded as unicode number in form of
1040  * &#xUUUU;
1041  *
1042  * To make bookmarks more readable for human (&#xUUUU certainly not) we add a
1043  * comment with '7-bit approximation' from the converted string.  This is a
1044  * valid HTML and bookmarks code.
1045  *
1046  * We do not want use META charset tag in bookmarks file:  it will never be
1047  * changed later :-(
1048  *
1049  * NCR's translation is part of I18N and HTML4.0 supported starting with Lynx
1050  * 2.7.2, Netscape 4.0 and MSIE 4.0.  Older versions fail.
1051  */
title_convert8bit(const char * Title)1052 static char *title_convert8bit(const char *Title)
1053 {
1054     const char *p = Title;
1055     char *p0;
1056     char *q;
1057     char *comment = NULL;
1058     char *ncr = NULL;
1059     char *buf = NULL;
1060     int charset_in = current_char_set;
1061     int charset_out = UCGetLYhndl_byMIME("us-ascii");
1062 
1063     for (; *p; p++) {
1064 	char temp[2];
1065 
1066 	LYStrNCpy(temp, p, sizeof(temp) - 1);
1067 	if (UCH(*temp) <= 127) {
1068 	    StrAllocCat(comment, temp);
1069 	    StrAllocCat(ncr, temp);
1070 	} else if (charset_out >= 0) {
1071 	    long unicode;
1072 	    char replace_buf[32];
1073 
1074 	    if (UCTransCharStr(replace_buf, (int) sizeof(replace_buf), *temp,
1075 			       charset_in, charset_out, YES) > 0)
1076 		StrAllocCat(comment, replace_buf);
1077 
1078 	    unicode = UCTransToUni(*temp, charset_in);
1079 
1080 	    StrAllocCat(ncr, "&#");
1081 	    sprintf(replace_buf, "%ld", unicode);
1082 	    StrAllocCat(ncr, replace_buf);
1083 	    StrAllocCat(ncr, ";");
1084 	}
1085     }
1086 
1087     if (comment != NULL) {
1088 	/*
1089 	 * Cleanup comment, collapse multiple dashes into one dash, skip '>'.
1090 	 */
1091 	for (q = p0 = comment; *p0; p0++) {
1092 	    if (UCH(TOASCII(*p0)) >= 32 &&
1093 		*p0 != '>' &&
1094 		(q == comment || *p0 != '-' || *(q - 1) != '-')) {
1095 		*q++ = *p0;
1096 	    }
1097 	}
1098 	*q = '\0';
1099 
1100 	/*
1101 	 * valid bookmark should be a single line (no linebreaks!).
1102 	 */
1103 	StrAllocCat(buf, "<!-- ");
1104 	StrAllocCat(buf, comment);
1105 	StrAllocCat(buf, " -->");
1106 	StrAllocCat(buf, ncr);
1107 
1108 	FREE(comment);
1109     }
1110     FREE(ncr);
1111     return (buf);
1112 }
1113 
1114 /*
1115  * Since this is the "Default Bookmark File", we save it as a global, and as
1116  * the first MBM_A_subbookmark entry.
1117  */
set_default_bookmark_page(char * value)1118 void set_default_bookmark_page(char *value)
1119 {
1120     if (value != 0) {
1121 	if (bookmark_page == NULL
1122 	    || strcmp(bookmark_page, value)) {
1123 	    StrAllocCopy(bookmark_page, value);
1124 	}
1125 	StrAllocCopy(BookmarkPage, bookmark_page);
1126 	StrAllocCopy(MBM_A_subbookmark[0], bookmark_page);
1127 	StrAllocCopy(MBM_A_subdescript[0], MULTIBOOKMARKS_DEFAULT);
1128     }
1129 }
1130