links

lynx-like text mode web browser
git clone anongit@rnpnr.xyz:links.git
Log | Files | Refs | Feed | README | LICENSE

bookmark.c (20339B)


      1 /* bookmark.c
      2  * (c) 2002 Petr 'Brain' Kulhavy, Karel 'Clock' Kulhavy
      3  * This file is a part of the Links program, released under GPL.
      4  */
      5 
      6 #include "links.h"
      7 
      8 static struct stat bookmarks_st;
      9 
     10 static struct list *bookmark_new_item(void *);
     11 static unsigned char *bookmark_type_item(struct terminal *, struct list *, int);
     12 static void bookmark_delete_item(struct list *);
     13 static void bookmark_edit_item(struct dialog_data *, struct list *,
     14                                void (*)(struct dialog_data *, struct list *,
     15                                         struct list *,
     16                                         struct list_description *),
     17                                struct list *, unsigned char);
     18 static void bookmark_copy_item(struct list *, struct list *);
     19 static void bookmark_goto_item(struct session *, struct list *);
     20 static void *bookmark_default_value(struct session *, unsigned char);
     21 static struct list *bookmark_find_item(struct list *start, unsigned char *str,
     22                                        int direction);
     23 static void save_bookmarks(struct session *ses);
     24 
     25 struct list bookmarks = { init_list_1st(&bookmarks.list_entry) 0, -1, NULL };
     26 
     27 static struct history bookmark_search_history = {
     28 	0, {&bookmark_search_history.items, &bookmark_search_history.items}
     29 };
     30 
     31 /* when you change anything, don't forget to change it in reinit_bookmarks too
     32  * !*/
     33 
     34 struct bookmark_ok_struct {
     35 	void (*fn)(struct dialog_data *, struct list *, struct list *,
     36 	           struct list_description *);
     37 	struct list *data;
     38 	struct dialog_data *dlg;
     39 };
     40 
     41 struct bookmark_list {
     42 	list_head_1st
     43 	    /* bookmark specific */
     44 	    unsigned char *title;
     45 	unsigned char *url;
     46 	list_head_last
     47 };
     48 
     49 static struct list_description bookmark_ld = {
     50 	1,                  /* 0= flat; 1=tree */
     51 	&bookmarks,         /* list */
     52 	bookmark_new_item,  /* no codepage translations */
     53 	bookmark_edit_item, /* translate when create dialog and translate back
     54 	                       when ok is pressed */
     55 	bookmark_default_value, /* codepage translation from
     56 	                           current_page_encoding to UTF8 */
     57 	bookmark_delete_item,   /* no codepage translations */
     58 	bookmark_copy_item,     /* no codepage translations */
     59 	bookmark_type_item,     /* no codepage translations (bookmarks are
     60 	                           internally in UTF8) */
     61 	bookmark_find_item,
     62 	&bookmark_search_history,
     63 	0,  /* codepage */
     64 	15, /* # of items in main window */
     65 	T_BOOKMARK,
     66 	T_BOOKMARKS_ALREADY_IN_USE,
     67 	T_BOOKMARK_MANAGER,
     68 	T_DELETE_BOOKMARK,
     69 	T_GOTO,
     70 	bookmark_goto_item, /* FIXME: should work (URL in UTF8), but who knows?
     71 	                     */
     72 	save_bookmarks,
     73 
     74 	NULL,
     75 	NULL,
     76 	0,
     77 	0, /* internal vars */
     78 	0, /* modified */
     79 	NULL,
     80 	NULL,
     81 	0,
     82 };
     83 
     84 struct kawasaki {
     85 	unsigned char *title;
     86 	unsigned char *url;
     87 };
     88 
     89 /* clears the bookmark list */
     90 static void
     91 free_bookmarks(void)
     92 {
     93 	struct list *b = NULL;
     94 	struct list_head *lb;
     95 
     96 	foreach (struct list, b, lb, bookmarks.list_entry) {
     97 		struct bookmark_list *bm =
     98 		    get_struct(b, struct bookmark_list, head);
     99 		free(bm->title);
    100 		free(bm->url);
    101 		lb = lb->prev;
    102 		del_from_list(b);
    103 		free(bm);
    104 	}
    105 }
    106 
    107 /* called before exiting the links */
    108 void
    109 finalize_bookmarks(void)
    110 {
    111 	free_bookmarks();
    112 	free_history(bookmark_search_history);
    113 }
    114 
    115 /* allocates struct kawasaki and puts current page title and url */
    116 /* type: 0=item, 1=directory */
    117 /* on error returns NULL */
    118 static void *
    119 bookmark_default_value(struct session *ses, unsigned char type)
    120 {
    121 	struct kawasaki *zelena;
    122 	unsigned char *txt;
    123 
    124 	txt = xmalloc(MAX_STR_LEN);
    125 
    126 	zelena = xmalloc(sizeof(struct kawasaki));
    127 
    128 	zelena->url = NULL;
    129 	zelena->title = NULL;
    130 	if (get_current_url(ses, txt, MAX_STR_LEN)) {
    131 		if (ses->screen->f_data) {
    132 			zelena->url = stracpy(txt);
    133 			clr_white(zelena->url);
    134 		} else
    135 			zelena->url = stracpy(txt);
    136 	}
    137 	/* ses->screen->f_data must exist here */
    138 	if (get_current_title(ses->screen, txt, MAX_STR_LEN)) {
    139 		zelena->title = stracpy(txt);
    140 		clr_white(zelena->title);
    141 	}
    142 
    143 	free(txt);
    144 
    145 	return zelena;
    146 }
    147 
    148 static void
    149 bookmark_copy_item(struct list *in, struct list *out)
    150 {
    151 	struct bookmark_list *item_in =
    152 	    get_struct(in, struct bookmark_list, head);
    153 	struct bookmark_list *item_out =
    154 	    get_struct(out, struct bookmark_list, head);
    155 
    156 	item_out->head.type = item_in->head.type;
    157 	item_out->head.depth = item_in->head.depth;
    158 
    159 	free(item_out->title);
    160 	item_out->title = stracpy(item_in->title);
    161 	free(item_out->url);
    162 	item_out->url = stracpy(item_in->url);
    163 }
    164 
    165 static unsigned char *const bm_add_msg[] = {
    166 	TEXT_(T_NNAME),
    167 	TEXT_(T_URL),
    168 };
    169 
    170 /* Called to setup the add bookmark dialog */
    171 static void
    172 bookmark_edit_item_fn(struct dialog_data *dlg)
    173 {
    174 	int max = 0, min = 0;
    175 	int w, rw;
    176 	int y = -1;
    177 	struct terminal *term;
    178 	int a;
    179 
    180 	term = dlg->win->term;
    181 
    182 	for (a = 0; a < dlg->n - 2; a++) {
    183 		max_text_width(term, bm_add_msg[a], &max, AL_LEFT);
    184 		min_text_width(term, bm_add_msg[a], &min, AL_LEFT);
    185 	}
    186 	max_buttons_width(term, dlg->items + dlg->n - 2, 2, &max);
    187 	min_buttons_width(term, dlg->items + dlg->n - 2, 2, &min);
    188 	w = term->x * 9 / 10 - 2 * DIALOG_LB;
    189 
    190 	if (w < min)
    191 		w = min;
    192 
    193 	rw = w;
    194 
    195 	for (a = 0; a < dlg->n - 2; a++, y++)
    196 		dlg_format_text_and_field(dlg, NULL, bm_add_msg[a],
    197 		                          &dlg->items[a], 0, &y, w, &rw,
    198 		                          COLOR_DIALOG_TEXT, AL_LEFT);
    199 	dlg_format_buttons(dlg, NULL, dlg->items + dlg->n - 2, 2, 0, &y, w, &rw,
    200 	                   AL_CENTER);
    201 	w = rw;
    202 	dlg->xw = w + 2 * DIALOG_LB;
    203 	dlg->yw = y + 2 * DIALOG_TB;
    204 	center_dlg(dlg);
    205 	draw_dlg(dlg);
    206 	y = dlg->y + DIALOG_TB;
    207 	for (a = 0; a < dlg->n - 2; a++, y++)
    208 		dlg_format_text_and_field(dlg, term, bm_add_msg[a],
    209 		                          &dlg->items[a], dlg->x + DIALOG_LB,
    210 		                          &y, w, NULL, COLOR_DIALOG_TEXT,
    211 		                          AL_LEFT);
    212 	dlg_format_buttons(dlg, term, &dlg->items[dlg->n - 2], 2,
    213 	                   dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER);
    214 }
    215 
    216 /* Puts url and title into the bookmark item */
    217 static void
    218 bookmark_edit_done(void *data)
    219 {
    220 	struct dialog *d = (struct dialog *)data;
    221 	struct bookmark_list *item = (struct bookmark_list *)d->udata;
    222 	unsigned char *title, *url;
    223 	struct bookmark_ok_struct *s = (struct bookmark_ok_struct *)d->udata2;
    224 	int a;
    225 
    226 	if (item->head.type & 1)
    227 		a = 4;
    228 	else
    229 		a = 5;
    230 	title = (unsigned char *)&d->items[a];
    231 	url = title + MAX_STR_LEN;
    232 
    233 	free(item->title);
    234 	item->title = stracpy(title);
    235 	clr_white(item->title);
    236 
    237 	free(item->url);
    238 	item->url = stracpy(url);
    239 	clr_white(item->url);
    240 
    241 	s->fn(s->dlg, s->data, &item->head, &bookmark_ld);
    242 	d->udata = NULL; /* for abort function */
    243 }
    244 
    245 /* destroys an item, this function is called when edit window is aborted */
    246 static void
    247 bookmark_edit_abort(struct dialog_data *data)
    248 {
    249 	struct bookmark_list *item = (struct bookmark_list *)data->dlg->udata;
    250 	struct dialog *dlg = data->dlg;
    251 
    252 	free(dlg->udata2);
    253 	if (item)
    254 		bookmark_delete_item(&item->head);
    255 }
    256 
    257 /* dlg_title is TITLE_EDIT or TITLE_ADD */
    258 /* edit item function */
    259 static void
    260 bookmark_edit_item(struct dialog_data *dlg, struct list *data,
    261                    void (*ok_fn)(struct dialog_data *, struct list *,
    262                                  struct list *, struct list_description *),
    263                    struct list *ok_arg, unsigned char dlg_title)
    264 {
    265 	struct bookmark_list *item =
    266 	    get_struct(data, struct bookmark_list, head);
    267 	unsigned char *title, *url, *txt;
    268 	struct dialog *d;
    269 	struct bookmark_ok_struct *s;
    270 	int a;
    271 
    272 	/* Create the dialog */
    273 	s = xmalloc(sizeof(struct bookmark_ok_struct));
    274 	s->fn = ok_fn;
    275 	s->data = ok_arg;
    276 	s->dlg = dlg;
    277 
    278 	if (item->head.type & 1)
    279 		a = 4; /* folder */
    280 	else
    281 		a = 5;
    282 	d = mem_calloc(sizeof(struct dialog) + a * sizeof(struct dialog_item)
    283 	               + 2 * MAX_STR_LEN);
    284 
    285 	title = (unsigned char *)&d->items[a];
    286 	url = title + MAX_STR_LEN;
    287 
    288 	txt = stracpy(item->title);
    289 	clr_white(txt);
    290 	safe_strncpy(title, txt, MAX_STR_LEN);
    291 	free(txt);
    292 
    293 	txt = stracpy(item->url);
    294 	clr_white(txt);
    295 	safe_strncpy(url, txt, MAX_STR_LEN);
    296 	free(txt);
    297 
    298 	switch (dlg_title) {
    299 	case TITLE_EDIT:
    300 		if (item->head.type & 1)
    301 			d->title = TEXT_(T_EDIT_FOLDER);
    302 		else
    303 			d->title = TEXT_(T_EDIT_BOOKMARK);
    304 		break;
    305 	case TITLE_ADD:
    306 		if (item->head.type & 1)
    307 			d->title = TEXT_(T_ADD_FOLDER);
    308 		else
    309 			d->title = TEXT_(T_ADD_BOOKMARK);
    310 		break;
    311 	default:
    312 		internal("Unsupported dialog title.\n");
    313 	}
    314 	d->fn = bookmark_edit_item_fn;
    315 	d->udata = item;
    316 	d->udata2 = s;
    317 	d->refresh = bookmark_edit_done;
    318 	d->abort = bookmark_edit_abort;
    319 	d->refresh_data = d;
    320 
    321 	d->items[0].type = D_FIELD;
    322 	d->items[0].dlen = MAX_STR_LEN;
    323 	d->items[0].data = title;
    324 	d->items[0].fn = check_nonempty;
    325 
    326 	a = 0;
    327 	if (!(item->head.type & 1)) {
    328 		d->items[1].type = D_FIELD;
    329 		d->items[1].dlen = MAX_STR_LEN;
    330 		d->items[1].data = url;
    331 		d->items[1].fn = check_nonempty;
    332 		a++;
    333 	}
    334 
    335 	d->items[a + 1].type = D_BUTTON;
    336 	d->items[a + 1].gid = B_ENTER;
    337 	d->items[a + 1].fn = ok_dialog;
    338 	d->items[a + 1].text = TEXT_(T_OK);
    339 
    340 	d->items[a + 2].type = D_BUTTON;
    341 	d->items[a + 2].gid = B_ESC;
    342 	d->items[a + 2].text = TEXT_(T_CANCEL);
    343 	d->items[a + 2].fn = cancel_dialog;
    344 
    345 	d->items[a + 3].type = D_END;
    346 
    347 	do_dialog(dlg->win->term, d, getml(d, NULL));
    348 }
    349 
    350 /* create new bookmark item and returns pointer to it, on error returns 0*/
    351 /* bookmark is filled with given data, data are deallocated afterwards */
    352 static struct list *
    353 bookmark_new_item(void *data)
    354 {
    355 	struct bookmark_list *b;
    356 	struct kawasaki *zelena = (struct kawasaki *)data;
    357 
    358 	b = xmalloc(sizeof(struct bookmark_list));
    359 
    360 	if (zelena && zelena->title)
    361 		b->title = zelena->title;
    362 	else
    363 		b->title = stracpy(cast_uchar "");
    364 
    365 	if (zelena && zelena->url)
    366 		b->url = zelena->url;
    367 	else
    368 		b->url = stracpy(cast_uchar "");
    369 
    370 	free(zelena);
    371 
    372 	return &b->head;
    373 }
    374 
    375 /* allocate string and print bookmark into it */
    376 /* x: 0=type all, 1=type title only */
    377 static unsigned char *
    378 bookmark_type_item(struct terminal *term, struct list *data, int x)
    379 {
    380 	unsigned char *txt, *txt1;
    381 	struct bookmark_list *item;
    382 
    383 	if (data == &bookmarks) /* head */
    384 		return stracpy(get_text_translation(TEXT_(T_BOOKMARKS), term));
    385 
    386 	item = get_struct(data, struct bookmark_list, head);
    387 	txt = stracpy(item->title);
    388 
    389 	if (!(item->head.type & 1)) {
    390 		add_to_strn(&txt, cast_uchar "   (");
    391 		add_to_strn(&txt, item->url);
    392 		add_to_strn(&txt, cast_uchar ")");
    393 	}
    394 
    395 	txt1 = stracpy(txt);
    396 	clr_white(txt1);
    397 	free(txt);
    398 	return txt1;
    399 }
    400 
    401 /* goto bookmark (called when goto button is pressed) */
    402 static void
    403 bookmark_goto_item(struct session *ses, struct list *i)
    404 {
    405 	struct bookmark_list *item = get_struct(i, struct bookmark_list, head);
    406 
    407 	goto_url_utf8(ses, item->url);
    408 }
    409 
    410 /* delete bookmark from list */
    411 static void
    412 bookmark_delete_item(struct list *data)
    413 {
    414 	struct bookmark_list *item =
    415 	    get_struct(data, struct bookmark_list, head);
    416 
    417 	if (item->head.list_entry.next)
    418 		del_from_list(&item->head);
    419 	free(item->url);
    420 	free(item->title);
    421 	free(item);
    422 }
    423 
    424 static int
    425 substr_utf8(unsigned char *string, unsigned char *substr)
    426 {
    427 	int r;
    428 	string = unicode_upcase_string(string);
    429 	substr = unicode_upcase_string(substr);
    430 	r = !!strstr((char *)string, (char *)substr);
    431 	free(string);
    432 	free(substr);
    433 	return r;
    434 }
    435 
    436 static int
    437 test_entry(struct list *e, unsigned char *str)
    438 {
    439 	struct bookmark_list *list = get_struct(e, struct bookmark_list, head);
    440 	if (substr_utf8(list->title, str))
    441 		return 1;
    442 	return casestrstr(list->url, str);
    443 }
    444 
    445 static struct list *
    446 bookmark_find_item(struct list *s, unsigned char *str, int direction)
    447 {
    448 	struct list *e;
    449 
    450 	if (direction >= 0)
    451 		for (e = list_next(s); e != s; e = list_next(e)) {
    452 			if (e->depth >= 0 && test_entry(e, str))
    453 				return e;
    454 		}
    455 	else
    456 		for (e = list_prev(s); e != s; e = list_prev(e))
    457 			if (e->depth >= 0 && test_entry(e, str))
    458 				return e;
    459 
    460 	if (e->depth >= 0 && test_entry(e, str))
    461 		return e;
    462 
    463 	return NULL;
    464 }
    465 
    466 /* returns previous item in the same folder and with same the depth, or father
    467  * if there's no previous item */
    468 /* we suppose that previous items have correct pointer fotr */
    469 static struct list *
    470 previous_on_this_level(struct list *item)
    471 {
    472 	struct list *p;
    473 
    474 	for (p = list_prev(item); p->depth > item->depth; p = p->fotr)
    475 		;
    476 	return p;
    477 }
    478 
    479 /* create new bookmark at the end of the list */
    480 /* if url is NULL, create folder */
    481 /* both strings are null terminated */
    482 static void
    483 add_bookmark(unsigned char *title, unsigned char *url, int depth)
    484 {
    485 	struct bookmark_list *b;
    486 	struct list *p;
    487 	struct document_options *dop;
    488 
    489 	if (!title)
    490 		return;
    491 
    492 	b = xmalloc(sizeof(struct bookmark_list));
    493 
    494 	dop = mem_calloc(sizeof(struct document_options));
    495 	dop->cp = 0;
    496 
    497 	b->title = stracpy(title);
    498 	clr_white(b->title);
    499 
    500 	if (url) {
    501 		b->url = stracpy(url);
    502 		clr_white(b->url);
    503 		b->head.type = 0;
    504 	} else {
    505 		b->url = stracpy(cast_uchar "");
    506 		b->head.type = 1;
    507 	}
    508 
    509 	b->head.depth = depth;
    510 
    511 	add_to_list_end(bookmarks.list_entry, &b->head);
    512 
    513 	p = previous_on_this_level(&b->head);
    514 	if (p->depth < b->head.depth)
    515 		/* directory b belongs into */
    516 		b->head.fotr = p;
    517 	else
    518 		b->head.fotr = p->fotr;
    519 
    520 	free(dop);
    521 }
    522 
    523 static void
    524 load_bookmarks(struct session *ses)
    525 {
    526 	unsigned char *buf;
    527 	long len;
    528 
    529 	unsigned char *p, *end;
    530 	unsigned char *name, *attr;
    531 	int namelen;
    532 	int status;
    533 	unsigned char *title = 0;
    534 	unsigned char *url = 0;
    535 	int depth;
    536 
    537 	struct document_options dop;
    538 	int rs;
    539 
    540 	memset(&dop, 0, sizeof(dop));
    541 	dop.plain = 1;
    542 
    543 	/* status:
    544 	 *	0 = find <dt> or </dl> element
    545 	 *	1 = find <a> or <h3> element
    546 	 *	2 = reading bookmark, find </a> element, title is pointer
    547 	 *	    behind the leading <a> element
    548 	 *	3 = reading folder name, find </h3> element, title is
    549 	 *	pointer behind leading <h3> element
    550 	 */
    551 
    552 	buf = read_config_file(bookmarks_file);
    553 	if (!buf) {
    554 		bookmark_ld.modified = 1;
    555 		save_bookmarks(ses);
    556 		return;
    557 	}
    558 
    559 	len = strlen(cast_const_char buf);
    560 
    561 	p = buf;
    562 	end = buf + len;
    563 
    564 	status = 0; /* find bookmark */
    565 	depth = 0;
    566 
    567 	d_opt = &dop;
    568 	while (1) {
    569 		unsigned char *s;
    570 
    571 		/* find start of html tag */
    572 		for (; p < end && *p != '<'; p++)
    573 			;
    574 
    575 		if (p >= end)
    576 			break;
    577 		s = p;
    578 		if (end - p >= 2 && (p[1] == '!' || p[1] == '?')) {
    579 			p = skip_comment(p, end);
    580 			continue;
    581 		}
    582 		if (parse_element(p, end, &name, &namelen, &attr, &p)) {
    583 			p++;
    584 			continue;
    585 		}
    586 
    587 		switch (status) {
    588 		case 0: /* <dt> or </dl> */
    589 			if (namelen == 2 && !casecmp(name, cast_uchar "dt", 2))
    590 				status = 1;
    591 			else if (namelen == 3
    592 			         && !casecmp(name, cast_uchar "/dl", 3)) {
    593 				depth--;
    594 				if (depth == -1)
    595 					goto smitec;
    596 			}
    597 			continue;
    598 
    599 		case 1: /* find "a" element */
    600 			if (namelen == 1 && !casecmp(name, cast_uchar "a", 1)) {
    601 				if (!(url = get_attr_val(attr,
    602 				                         cast_uchar "href")))
    603 					continue;
    604 				status = 2;
    605 				title = p;
    606 			} else if (namelen == 2
    607 			           && !casecmp(name, cast_uchar "h3", 1)) {
    608 				status = 3;
    609 				title = p;
    610 			}
    611 			continue;
    612 
    613 		case 2: /* find "/a" element */
    614 			if (namelen != 2 || casecmp(name, cast_uchar "/a", 2))
    615 				/* ignore all other elements */
    616 				continue;
    617 			*s = 0;
    618 			add_bookmark(title, url, depth);
    619 			free(url);
    620 			status = 0;
    621 			continue;
    622 
    623 		case 3: /* find "/h3" element */
    624 			if (namelen != 3 || casecmp(name, cast_uchar "/h3", 2))
    625 				/* ignore all other elements */
    626 				continue;
    627 			*s = 0;
    628 			add_bookmark(title, NULL, depth);
    629 			status = 0;
    630 			depth++;
    631 			continue;
    632 		}
    633 	}
    634 	if (status == 2)
    635 		free(url);
    636 smitec:
    637 	free(buf);
    638 	d_opt = &dd_opt;
    639 	bookmark_ld.modified = 0;
    640 
    641 	EINTRLOOP(rs, stat((char *)bookmarks_file, &bookmarks_st));
    642 	if (rs)
    643 		memset(&bookmarks_st, -1, sizeof bookmarks_st);
    644 }
    645 
    646 void
    647 init_bookmarks(void)
    648 {
    649 	memset(&bookmarks_st, -1, sizeof bookmarks_st);
    650 	if (!*bookmarks_file) {
    651 		unsigned char *e;
    652 		safe_strncpy(bookmarks_file,
    653 		             links_home ? links_home : (unsigned char *)"",
    654 		             MAX_STR_LEN);
    655 		e = cast_uchar strchr((char *)bookmarks_file, 0);
    656 		safe_strncpy(e, cast_uchar "bookmarks.html",
    657 		             MAX_STR_LEN - (e - bookmarks_file));
    658 	}
    659 
    660 	load_bookmarks(NULL);
    661 }
    662 
    663 void
    664 reinit_bookmarks(struct session *ses, unsigned char *new_bookmarks_file)
    665 {
    666 	unsigned char *buf;
    667 	if (test_list_window_in_use(&bookmark_ld, ses->term))
    668 		return;
    669 
    670 	if (!strcmp((char *)bookmarks_file, (char *)new_bookmarks_file))
    671 		goto save_only;
    672 
    673 	buf = read_config_file(new_bookmarks_file);
    674 	if (buf) {
    675 		free(buf);
    676 		free_bookmarks();
    677 		safe_strncpy(bookmarks_file, new_bookmarks_file, MAX_STR_LEN);
    678 		load_bookmarks(ses);
    679 		reinit_list_window(&bookmark_ld);
    680 	} else {
    681 save_only:
    682 		safe_strncpy(bookmarks_file, new_bookmarks_file, MAX_STR_LEN);
    683 		bookmark_ld.modified = 1;
    684 		save_bookmarks(ses);
    685 	}
    686 }
    687 
    688 /* gets str, converts all < = > & to appropriate entity
    689  * returns allocated string with result
    690  */
    691 static unsigned char *
    692 convert_to_entity_string(unsigned char *str)
    693 {
    694 	unsigned char *dst, *p = NULL;
    695 	size_t dstl = 0;
    696 
    697 	for (p = str; *p; p++)
    698 		switch (*p) {
    699 		case '<':
    700 			dstl = add_to_str(&dst, dstl, cast_uchar "&lt;");
    701 			break;
    702 		case '>':
    703 			dstl = add_to_str(&dst, dstl, cast_uchar "&gt;");
    704 			break;
    705 
    706 		case '=':
    707 			dstl = add_to_str(&dst, dstl, cast_uchar "&equals;");
    708 			break;
    709 
    710 		case '&':
    711 			dstl = add_to_str(&dst, dstl, cast_uchar "&amp;");
    712 			break;
    713 
    714 		case '"':
    715 			dstl = add_to_str(&dst, dstl, cast_uchar "&quot;");
    716 			break;
    717 
    718 		default:
    719 			dstl = add_chr_to_str(&dst, dstl, *p);
    720 		}
    721 	return dst;
    722 }
    723 
    724 /* writes bookmarks to disk */
    725 static void
    726 save_bookmarks(struct session *ses)
    727 {
    728 	struct list *li = NULL;
    729 	struct list_head *lli;
    730 	int depth;
    731 	int a;
    732 	unsigned char *data;
    733 	size_t l;
    734 	int err;
    735 	int rs;
    736 
    737 	if (!bookmark_ld.modified)
    738 		return;
    739 	data = NULL;
    740 	l = add_to_str(&data, 0,
    741 	               cast_uchar
    742 	               "<HTML>\n"
    743 	               "<HEAD>\n"
    744 	               "<!-- This is an automatically generated file.\n"
    745 	               "It will be read and overwritten.\n"
    746 	               "Do Not Edit! -->\n"
    747 	               "<TITLE>Links bookmarks</TITLE>\n"
    748 	               "</HEAD>\n"
    749 	               "<H1>Links bookmarks</H1>\n\n"
    750 	               "<DL><P>\n");
    751 	depth = 0;
    752 	foreach (struct list, li, lli, bookmarks.list_entry) {
    753 		struct bookmark_list *b =
    754 		    get_struct(li, struct bookmark_list, head);
    755 		for (a = b->head.depth; a < depth; a++)
    756 			l = add_to_str(&data, l, cast_uchar "</DL>\n");
    757 		depth = b->head.depth;
    758 
    759 		if (b->head.type & 1) {
    760 			unsigned char *txt, *txt1;
    761 			txt = stracpy(b->title);
    762 			clr_white(txt);
    763 			txt1 = convert_to_entity_string(txt);
    764 			l = add_to_str(&data, l, cast_uchar "    <DT><H3>");
    765 			l = add_to_str(&data, l, txt1);
    766 			l = add_to_str(&data, l, cast_uchar "</H3>\n<DL>\n");
    767 			free(txt);
    768 			free(txt1);
    769 			depth++;
    770 		} else {
    771 			unsigned char *txt1, *txt2, *txt11;
    772 			txt1 = stracpy(b->title);
    773 			clr_white(txt1);
    774 			txt2 = stracpy(b->url);
    775 			clr_white(txt2);
    776 			txt11 = convert_to_entity_string(txt1);
    777 			l = add_to_str(&data, l,
    778 			               cast_uchar "    <DT><A HREF=\"");
    779 			l = add_to_str(&data, l, txt2);
    780 			l = add_to_str(&data, l, cast_uchar "\">");
    781 			l = add_to_str(&data, l, txt11);
    782 			l = add_to_str(&data, l, cast_uchar "</A>\n");
    783 			free(txt1);
    784 			free(txt2);
    785 			free(txt11);
    786 		}
    787 	}
    788 	for (a = 0; a < depth; a++)
    789 		l = add_to_str(&data, l, cast_uchar "</DL>\n");
    790 	l = add_to_str(&data, l,
    791 	               cast_uchar "</DL><P>\n"
    792 	                          "</HTML>\n");
    793 	err = write_to_config_file(bookmarks_file, data, 1);
    794 	free(data);
    795 	if (!err)
    796 		bookmark_ld.modified = 0;
    797 	else if (ses) {
    798 		unsigned char *f = stracpy(bookmarks_file);
    799 		msg_box(ses->term, getml(f, NULL), TEXT_(T_BOOKMARK_ERROR),
    800 		        AL_CENTER, TEXT_(T_UNABLE_TO_WRITE_TO_BOOKMARK_FILE),
    801 		        cast_uchar " ", f, cast_uchar ": ", get_err_msg(err),
    802 		        MSG_BOX_END, NULL, 1, TEXT_(T_CANCEL), msg_box_null,
    803 		        B_ENTER | B_ESC);
    804 	}
    805 
    806 	EINTRLOOP(rs, stat(cast_const_char bookmarks_file, &bookmarks_st));
    807 	if (rs)
    808 		memset(&bookmarks_st, -1, sizeof bookmarks_st);
    809 }
    810 
    811 void
    812 menu_bookmark_manager(struct terminal *term, void *fcp, void *ses_)
    813 {
    814 	struct session *ses = (struct session *)ses_;
    815 	struct stat st;
    816 	int rs;
    817 	EINTRLOOP(rs, stat(cast_const_char bookmarks_file, &st));
    818 	if (!rs
    819 	    && (st.st_ctime != bookmarks_st.st_ctime
    820 	        || st.st_mtime != bookmarks_st.st_mtime
    821 	        || st.st_size != bookmarks_st.st_size)) {
    822 		if (!test_list_window_in_use(&bookmark_ld, NULL)) {
    823 			free_bookmarks();
    824 			load_bookmarks(ses);
    825 			reinit_list_window(&bookmark_ld);
    826 		}
    827 	}
    828 	create_list_window(&bookmark_ld, &bookmarks, term, ses);
    829 }