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