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 < entities
312 * and any ampersands converted to & 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 & 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