/* types.c
 * (c) 2002 Mikulas Patocka
 * This file is a part of the Links program, released under GPL.
 */

#include "links.h"


/*------------------------ ASSOCIATIONS -----------------------*/

/* DECLARATIONS */

static void assoc_edit_item(struct dialog_data *, struct list *, void (*)(struct dialog_data *, struct list *, struct list *, struct list_description *), struct list *, unsigned char);
static void assoc_copy_item(struct list *, struct list *);
static struct list *assoc_new_item(void *);
static void assoc_delete_item(struct list *);
static struct list *assoc_find_item(struct list *start, unsigned char *str, int direction);
static unsigned char *assoc_type_item(struct terminal *, struct list *, int);


struct list assoc = { init_list_1st(&assoc.list_entry) 0, -1, NULL, init_list_last(&assoc.list_entry) };

static struct history assoc_search_history = { 0, { &assoc_search_history.items, &assoc_search_history.items } };

struct assoc_ok_struct {
	void (*fn)(struct dialog_data *, struct list *, struct list *, struct list_description *);
	struct list *data;
	struct dialog_data *dlg;
};


static struct list_description assoc_ld={
	0,  /* 0= flat; 1=tree */
	&assoc,  /* list */
	assoc_new_item,
	assoc_edit_item,
	NULL,
	assoc_delete_item,
	assoc_copy_item,
	assoc_type_item,
	assoc_find_item,
	&assoc_search_history,
	0,		/* this is set in init_assoc function */
	15,  /* # of items in main window */
	T_ASSOCIATION,
	T_ASSOCIATIONS_ALREADY_IN_USE,
	T_ASSOCIATIONS_MANAGER,
	T_DELETE_ASSOCIATION,
	0,	/* no button */
	NULL,	/* no button */
	NULL,	/* no save*/

	NULL,NULL,0,0,  /* internal vars */
	0, /* modified */
	NULL,
	NULL,
	1,
};

static struct list *assoc_new_item(void *ignore)
{
	struct assoc *neww;

	neww = mem_calloc(sizeof(struct assoc));
	neww->label = stracpy(cast_uchar "");
	neww->ct = stracpy(cast_uchar "");
	neww->prog = stracpy(cast_uchar "");
	neww->block = neww->xwin = neww->cons = 1;
	neww->ask = 1;
	neww->accept_http = 0;
	neww->accept_ftp = 0;
	neww->head.type = 0;
	neww->system = SYSTEM_ID;
	return &neww->head;
}

static void assoc_delete_item(struct list *data)
{
	struct assoc *del = get_struct(data, struct assoc, head);

	if (del->head.list_entry.next) del_from_list(&del->head);
	mem_free(del->label);
	mem_free(del->ct);
	mem_free(del->prog);
	mem_free(del);
}

static void assoc_copy_item(struct list *in, struct list *out)
{
	struct assoc *item_in = get_struct(in, struct assoc, head);
	struct assoc *item_out = get_struct(out, struct assoc, head);

	item_out->cons = item_in->cons;
	item_out->xwin = item_in->xwin;
	item_out->block = item_in->block;
	item_out->ask = item_in->ask;
	item_out->accept_http = item_in->accept_http;
	item_out->accept_ftp = item_in->accept_ftp;
	item_out->system = item_in->system;

	mem_free(item_out->label);
	mem_free(item_out->ct);
	mem_free(item_out->prog);

	item_out->label = stracpy(item_in->label);
	item_out->ct = stracpy(item_in->ct);
	item_out->prog = stracpy(item_in->prog);
}

/* allocate string and print association into it */
/* x: 0=type all, 1=type title only */
static unsigned char *assoc_type_item(struct terminal *term, struct list *data, int x)
{
	unsigned char *txt, *txt1;
	struct assoc *item;

	if (data == &assoc) return stracpy(get_text_translation(TEXT_(T_ASSOCIATIONS), term));

	item = get_struct(data, struct assoc, head);
	txt = stracpy(cast_uchar "");
	if (item->system != SYSTEM_ID) add_to_strn(&txt, cast_uchar "XX ");
	add_to_strn(&txt, item->label);
	add_to_strn(&txt, cast_uchar ": ");
	add_to_strn(&txt, item->ct);
	if (!x) {
		add_to_strn(&txt, cast_uchar " -> ");
		if (item->prog) add_to_strn(&txt, item->prog);
	}
	txt1 = convert(assoc_ld.codepage, term_charset(term), txt, NULL);
	mem_free(txt);

	return txt1;
}

void menu_assoc_manager(struct terminal *term, void *fcp, void *ses_)
{
	struct session *ses = (struct session *)ses_;
	create_list_window(&assoc_ld,&assoc, term, ses);
}

static unsigned char * const ct_msg[] = {
	TEXT_(T_LABEL),
	TEXT_(T_CONTENT_TYPES),
	TEXT_(T_PROGRAM__IS_REPLACED_WITH_FILE_NAME),
#ifdef ASSOC_BLOCK
	TEXT_(T_BLOCK_TERMINAL_WHILE_PROGRAM_RUNNING),
#endif
#ifdef ASSOC_CONS_XWIN
	TEXT_(T_RUN_ON_TERMINAL),
	TEXT_(T_RUN_IN_XWINDOW),
#endif
	TEXT_(T_ASK_BEFORE_OPENING),
	TEXT_(T_ACCEPT_HTTP),
	TEXT_(T_ACCEPT_FTP),
};

static void assoc_edit_item_fn(struct dialog_data *dlg)
{
	struct terminal *term = dlg->win->term;
	int max = 0, min = 0;
	int w, rw;
	int y = gf_val(-1, -G_BFU_FONT_SIZE);
	int p = 3;
	if (dlg->win->term->spec->braille) y += gf_val(1, G_BFU_FONT_SIZE);
#ifdef ASSOC_BLOCK
	p++;
#endif
#ifdef ASSOC_CONS_XWIN
	p += 2;
#endif
	max_text_width(term, ct_msg[0], &max, AL_LEFT);
	min_text_width(term, ct_msg[0], &min, AL_LEFT);
	max_text_width(term, ct_msg[1], &max, AL_LEFT);
	min_text_width(term, ct_msg[1], &min, AL_LEFT);
	max_text_width(term, ct_msg[2], &max, AL_LEFT);
	min_text_width(term, ct_msg[2], &min, AL_LEFT);
	max_group_width(term, ct_msg + 3, dlg->items + 3, p, &max);
	min_group_width(term, ct_msg + 3, dlg->items + 3, p, &min);
	max_buttons_width(term, dlg->items + 3 + p, 2, &max);
	min_buttons_width(term, dlg->items + 3 + p, 2, &min);
	w = term->x * 9 / 10 - 2 * DIALOG_LB;
	if (w > max) w = max;
	if (w < min) w = min;
	if (w > term->x - 2 * DIALOG_LB) w = term->x - 2 * DIALOG_LB;
	if (w < 1) w = 1;
	rw = 0;
	dlg_format_text_and_field(dlg, NULL, get_text_translation(ct_msg[0], term), &dlg->items[0], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE * 1);
	dlg_format_text_and_field(dlg, NULL, get_text_translation(ct_msg[1], term), &dlg->items[1], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE * 1);
	dlg_format_text_and_field(dlg, NULL, get_text_translation(ct_msg[2], term), &dlg->items[2], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE * 1);
	dlg_format_group(dlg, NULL, ct_msg + 3, dlg->items + 3, p, 0, &y, w, &rw);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_buttons(dlg, NULL, dlg->items + 3 + p, 2, 0, &y, w, &rw, AL_CENTER);
	w = rw;
	dlg->xw = w + 2 * DIALOG_LB;
	dlg->yw = y + 2 * DIALOG_TB;
	center_dlg(dlg);
	draw_dlg(dlg);
	y = dlg->y + DIALOG_TB;
	if (dlg->win->term->spec->braille) y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_text_and_field(dlg, term, ct_msg[0], &dlg->items[0], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_text_and_field(dlg, term, ct_msg[1], &dlg->items[1], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_text_and_field(dlg, term, ct_msg[2], &dlg->items[2], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_group(dlg, term, ct_msg + 3, &dlg->items[3], p, dlg->x + DIALOG_LB, &y, w, NULL);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_buttons(dlg, term, &dlg->items[3 + p], 2, dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER);
}

/* Puts url and title into the bookmark item */
static void assoc_edit_done(void *data)
{
	struct dialog *d = (struct dialog *)data;
	struct assoc *item = (struct assoc *)d->udata;
	struct assoc_ok_struct *s = (struct assoc_ok_struct *)d->udata2;
	unsigned char *txt;
	unsigned char *label, *ct, *prog;

	label = (unsigned char *)&d->items[12];
	ct = label + MAX_STR_LEN;
	prog = ct + MAX_STR_LEN;

	txt = convert(term_charset(s->dlg->win->term), assoc_ld.codepage, label, NULL);
	mem_free(item->label);
	item->label = txt;

	txt = convert(term_charset(s->dlg->win->term), assoc_ld.codepage, ct, NULL);
	mem_free(item->ct);
	item->ct = txt;

	txt = convert(term_charset(s->dlg->win->term), assoc_ld.codepage, prog, NULL);
	mem_free(item->prog);
	item->prog = txt;

	s->fn(s->dlg, s->data, &item->head, &assoc_ld);
	d->udata = NULL;  /* for abort function */
}

/* destroys an item, this function is called when edit window is aborted */
static void assoc_edit_abort(struct dialog_data *data)
{
	struct dialog *dlg = data->dlg;
	struct assoc *item = (struct assoc *)dlg->udata;

	mem_free(dlg->udata2);
	if (item) assoc_delete_item(&item->head);
}

static void assoc_edit_item(struct dialog_data *dlg, struct list *data, void (*ok_fn)(struct dialog_data *, struct list *, struct list *, struct list_description *), struct list *ok_arg, unsigned char dlg_title)
{
	int p;
	struct assoc *neww = get_struct(data, struct assoc, head);
	struct terminal *term = dlg->win->term;
	struct dialog *d;
	struct assoc_ok_struct *s;
	unsigned char *ct, *prog, *label;

	d = mem_calloc(sizeof(struct dialog) + 11 * sizeof(struct dialog_item) + 3 * MAX_STR_LEN);

	label = (unsigned char *)&d->items[12];
	ct = label + MAX_STR_LEN;
	prog = ct + MAX_STR_LEN;

	safe_strncpy(label, neww->label, MAX_STR_LEN);
	safe_strncpy(ct, neww->ct, MAX_STR_LEN);
	safe_strncpy(prog, neww->prog, MAX_STR_LEN);

	/* Create the dialog */
	s = mem_alloc(sizeof(struct assoc_ok_struct));
	s->fn = ok_fn;
	s->data = ok_arg;
	s->dlg = dlg;

	switch (dlg_title) {
		case TITLE_EDIT:
			d->title = TEXT_(T_EDIT_ASSOCIATION);
			break;

		case TITLE_ADD:
			d->title = TEXT_(T_ADD_ASSOCIATION);
			break;

		default:
			internal_error("Unsupported dialog title.\n");
	}

	d->udata = neww;
	d->udata2 = s;
	d->fn = assoc_edit_item_fn;
	d->abort = assoc_edit_abort;
	d->refresh = assoc_edit_done;
	d->refresh_data = d;
	d->items[0].type = D_FIELD;
	d->items[0].dlen = MAX_STR_LEN;
	d->items[0].data = label;
	d->items[0].fn = check_nonempty;
	d->items[1].type = D_FIELD;
	d->items[1].dlen = MAX_STR_LEN;
	d->items[1].data = ct;
	d->items[1].fn = check_nonempty;
	d->items[2].type = D_FIELD;
	d->items[2].dlen = MAX_STR_LEN;
	d->items[2].data = prog;
	d->items[2].fn = check_nonempty;
	p = 3;
#ifdef ASSOC_BLOCK
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->block;
	d->items[p++].dlen = sizeof(int);
#endif
#ifdef ASSOC_CONS_XWIN
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->cons;
	d->items[p++].dlen = sizeof(int);
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->xwin;
	d->items[p++].dlen = sizeof(int);
#endif
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->ask;
	d->items[p++].dlen = sizeof(int);
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->accept_http;
	d->items[p++].dlen = sizeof(int);
	d->items[p].type = D_CHECKBOX;
	d->items[p].data = (unsigned char *)&neww->accept_ftp;
	d->items[p++].dlen = sizeof(int);
	d->items[p].type = D_BUTTON;
	d->items[p].gid = B_ENTER;
	d->items[p].fn = ok_dialog;
	d->items[p++].text = TEXT_(T_OK);
	d->items[p].type = D_BUTTON;
	d->items[p].gid = B_ESC;
	d->items[p].text = TEXT_(T_CANCEL);
	d->items[p++].fn = cancel_dialog;
	d->items[p++].type = D_END;
	do_dialog(term, d, getml(d, NULL));
}

static int assoc_test_entry(struct list *e, unsigned char *str)
{
	struct assoc *a = get_struct(e, struct assoc, head);
	return casestrstr(a->label, str) || casestrstr(a->ct, str);
}

static struct list *assoc_find_item(struct list *s, unsigned char *str, int direction)
{
	struct list *e;

	if (direction >= 0) {
		for (e = list_next(s); e != s; e = list_next(e)) {
			if (e->depth >= 0 && assoc_test_entry(e, str))
				return e;
		}
	} else {
		for (e = list_prev(s); e != s; e = list_prev(e)) {
			if (e->depth >= 0 && assoc_test_entry(e, str))
				return e;
		}
	}

	if (e->depth >= 0 && assoc_test_entry(e, str))
		return e;

	return NULL;
}


void update_assoc(struct assoc *neww)
{
	struct assoc *repl;
	struct list *r;
	struct list_head *lr;
	if (!neww->label[0] || !neww->ct[0] || !neww->prog[0]) return;
	foreach(struct list, r, lr, assoc.list_entry) {
		repl = get_struct(r, struct assoc, head);
		if (!strcmp(cast_const_char repl->label, cast_const_char neww->label)
		 && !strcmp(cast_const_char repl->ct, cast_const_char neww->ct)
		 && !strcmp(cast_const_char repl->prog, cast_const_char neww->prog)
		 && repl->block == neww->block
		 && repl->cons == neww->cons
		 && repl->xwin == neww->xwin
		 && repl->ask == neww->ask
		 && repl->accept_http == neww->accept_http
		 && repl->accept_ftp == neww->accept_ftp
		 && repl->system == neww->system) {
			del_from_list(&repl->head);
			add_to_list(assoc.list_entry, &repl->head);
			return;
		}
	}
	repl = mem_calloc(sizeof(struct assoc));
	repl->label = stracpy(neww->label);
	repl->ct = stracpy(neww->ct);
	repl->prog = stracpy(neww->prog);
	repl->block = neww->block;
	repl->cons = neww->cons;
	repl->xwin = neww->xwin;
	repl->ask = neww->ask;
	repl->accept_http = neww->accept_http;
	repl->accept_ftp = neww->accept_ftp;
	repl->system = neww->system;
	repl->head.type = 0;
	add_to_list(assoc.list_entry, &repl->head);
}

/*------------------------ EXTENSIONS -----------------------*/

/* DECLARATIONS */
static void ext_edit_item(struct dialog_data *, struct list *, void (*)(struct dialog_data *, struct list *, struct list *, struct list_description *), struct list *, unsigned char);
static void ext_copy_item(struct list *, struct list *);
static struct list *ext_new_item(void *);
static void ext_delete_item(struct list *);
static struct list *ext_find_item(struct list *start, unsigned char *str, int direction);
static unsigned char *ext_type_item(struct terminal *, struct list *, int);

struct list extensions = { init_list_1st(&extensions.list_entry) 0, -1, NULL, init_list_last(&extensions.list_entry)  };

static struct history ext_search_history = { 0, { &ext_search_history.items, &ext_search_history.items } };


static struct list_description ext_ld={
	0,  /* 0= flat; 1=tree */
	&extensions,  /* list */
	ext_new_item,
	ext_edit_item,
	NULL,
	ext_delete_item,
	ext_copy_item,
	ext_type_item,
	ext_find_item,
	&ext_search_history,
	0,		/* this is set in init_assoc function */
	15,  /* # of items in main window */
	T_eXTENSION,
	T_EXTENSIONS_ALREADY_IN_USE,
	T_EXTENSIONS_MANAGER,
	T_DELETE_EXTENSION,
	0,	/* no button */
	NULL,	/* no button */
	NULL,	/* no save*/

	NULL,NULL,0,0,  /* internal vars */
	0, /* modified */
	NULL,
	NULL,
	0,
};





static struct list *ext_new_item(void *ignore)
{
	struct extension *neww;

	neww = mem_calloc(sizeof(struct extension));
	neww->ext = stracpy(cast_uchar "");
	neww->ct = stracpy(cast_uchar "");
	neww->head.type = 0;
	return &neww->head;
}


static void ext_delete_item(struct list *data)
{
	struct extension *del = get_struct(data, struct extension, head);

	if (del->head.list_entry.next) del_from_list(&del->head);
	if (del->ext) mem_free(del->ext);
	if (del->ct) mem_free(del->ct);
	mem_free(del);
}


static void ext_copy_item(struct list *in, struct list *out)
{
	struct extension *item_in = get_struct(in, struct extension, head);
	struct extension *item_out = get_struct(out, struct extension, head);

	mem_free(item_out->ext);
	mem_free(item_out->ct);

	item_out->ext = stracpy(item_in->ext);
	item_out->ct = stracpy(item_in->ct);
}


/* allocate string and print extension into it */
/* x: 0=type all, 1=type title only */
static unsigned char *ext_type_item(struct terminal *term, struct list *data, int x)
{
	unsigned char *txt, *txt1;
	struct extension *item;

	if (data == &extensions) return stracpy(get_text_translation(TEXT_(T_FILE_EXTENSIONS), term));

	item = get_struct(data, struct extension, head);
	txt = stracpy(item->ext);
	add_to_strn(&txt, cast_uchar ": ");
	add_to_strn(&txt, item->ct);
	txt1 = convert(assoc_ld.codepage, term_charset(term), txt, NULL);
	mem_free(txt);

	return txt1;
}


void menu_ext_manager(struct terminal *term, void *fcp, void *ses_)
{
	struct session *ses = (struct session *)ses_;
	create_list_window(&ext_ld, &extensions, term, ses);
}

static unsigned char * const ext_msg[] = {
	TEXT_(T_EXTENSION_S),
	TEXT_(T_CONTENT_TYPE),
};

static void ext_edit_item_fn(struct dialog_data *dlg)
{
	struct terminal *term = dlg->win->term;
	int max = 0, min = 0;
	int w, rw;
	int y = gf_val(-1, -G_BFU_FONT_SIZE);
	if (dlg->win->term->spec->braille) y += gf_val(1, G_BFU_FONT_SIZE);
	max_text_width(term, ext_msg[0], &max, AL_LEFT);
	min_text_width(term, ext_msg[0], &min, AL_LEFT);
	max_text_width(term, ext_msg[1], &max, AL_LEFT);
	min_text_width(term, ext_msg[1], &min, AL_LEFT);
	max_buttons_width(term, dlg->items + 2, 2, &max);
	min_buttons_width(term, dlg->items + 2, 2, &min);
	w = term->x * 9 / 10 - 2 * DIALOG_LB;
	if (w > max) w = max;
	if (w < min) w = min;
	if (w > term->x - 2 * DIALOG_LB) w = term->x - 2 * DIALOG_LB;
	if (w < 1) w = 1;
	rw = 0;
	dlg_format_text_and_field(dlg, NULL, ext_msg[0], &dlg->items[0], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE * 1);
	dlg_format_text_and_field(dlg, NULL, ext_msg[1], &dlg->items[1], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE * 1);
	dlg_format_buttons(dlg, NULL, dlg->items + 2, 2, 0, &y, w, &rw, AL_CENTER);
	w = rw;
	dlg->xw = w + 2 * DIALOG_LB;
	dlg->yw = y + 2 * DIALOG_TB;
	center_dlg(dlg);
	draw_dlg(dlg);
	y = dlg->y + DIALOG_TB;
	if (dlg->win->term->spec->braille) y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_text_and_field(dlg, term, ext_msg[0], &dlg->items[0], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_text_and_field(dlg, term, ext_msg[1], &dlg->items[1], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y += gf_val(1, G_BFU_FONT_SIZE);
	dlg_format_buttons(dlg, term, &dlg->items[2], 2, dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER);
}


/* Puts url and title into the bookmark item */
static void ext_edit_done(void *data)
{
	struct dialog *d = (struct dialog*)data;
	struct extension *item = (struct extension *)d->udata;
	struct assoc_ok_struct *s = (struct assoc_ok_struct *)d->udata2;
	unsigned char *txt;
	unsigned char *ext, *ct;

	ext = (unsigned char *)&d->items[5];
	ct = ext + MAX_STR_LEN;

	txt = convert(term_charset(s->dlg->win->term), ext_ld.codepage,ext, NULL);
	mem_free(item->ext);
	item->ext = txt;

	txt = convert(term_charset(s->dlg->win->term), ext_ld.codepage,ct, NULL);
	mem_free(item->ct);
	item->ct=txt;

	s->fn(s->dlg, s->data, &item->head, &ext_ld);
	d->udata = NULL;  /* for abort function */
}


/* destroys an item, this function is called when edit window is aborted */
static void ext_edit_abort(struct dialog_data *data)
{
	struct dialog *dlg = data->dlg;
	struct extension *item = (struct extension *)dlg->udata;

	mem_free(dlg->udata2);
	if (item) ext_delete_item(&item->head);
}


static void ext_edit_item(struct dialog_data *dlg, struct list *data, void (*ok_fn)(struct dialog_data *, struct list *, struct list *, struct list_description *), struct list *ok_arg, unsigned char dlg_title)
{
	struct extension *neww = get_struct(data, struct extension, head);
	struct terminal *term = dlg->win->term;
	struct dialog *d;
	struct assoc_ok_struct *s;
	unsigned char *ext;
	unsigned char *ct;

	d = mem_calloc(sizeof(struct dialog) + 4 * sizeof(struct dialog_item) + 2 * MAX_STR_LEN);

	ext = (unsigned char *)&d->items[5];
	ct = ext + MAX_STR_LEN;
	safe_strncpy(ext, neww->ext, MAX_STR_LEN);
	safe_strncpy(ct, neww->ct, MAX_STR_LEN);

	/* Create the dialog */
	s = mem_alloc(sizeof(struct assoc_ok_struct));
	s->fn = ok_fn;
	s->data = ok_arg;
	s->dlg = dlg;

	switch (dlg_title) {
		case TITLE_EDIT:
			d->title = TEXT_(T_EDIT_EXTENSION);
			break;

		case TITLE_ADD:
			d->title = TEXT_(T_ADD_EXTENSION);
			break;

		default:
			internal_error("Unsupported dialog title.\n");
	}

	d->udata = neww;
	d->udata2 = s;
	d->abort = ext_edit_abort;
	d->refresh = ext_edit_done;
	d->refresh_data = d;
	d->title = TEXT_(T_EXTENSION);
	d->fn = ext_edit_item_fn;
	d->items[0].type = D_FIELD;
	d->items[0].dlen = MAX_STR_LEN;
	d->items[0].data = ext;
	d->items[0].fn = check_nonempty;
	d->items[1].type = D_FIELD;
	d->items[1].dlen = MAX_STR_LEN;
	d->items[1].data = ct;
	d->items[1].fn = check_nonempty;
	d->items[2].type = D_BUTTON;
	d->items[2].gid = B_ENTER;
	d->items[2].fn = ok_dialog;
	d->items[2].text = TEXT_(T_OK);
	d->items[3].type = D_BUTTON;
	d->items[3].gid = B_ESC;
	d->items[3].text = TEXT_(T_CANCEL);
	d->items[3].fn = cancel_dialog;
	d->items[4].type = D_END;
	do_dialog(term, d, getml(d, NULL));
}


static int ext_test_entry(struct list *e, unsigned char *str)
{
	struct extension *ext = get_struct(e, struct extension, head);
	return casestrstr(ext->ext, str) || casestrstr(ext->ct, str);
}

static struct list *ext_find_item(struct list *s, unsigned char *str, int direction)
{
	struct list *e;

	if (direction >= 0) {
		for (e = list_next(s); e != s; e = list_next(e)) {
			if (e->depth >= 0 && ext_test_entry(e, str))
				return e;
		}
	} else {
		for (e = list_prev(s); e != s; e = list_prev(e)) {
			if (e->depth >= 0 && ext_test_entry(e, str))
				return e;
		}
	}

	if (e->depth >= 0 && ext_test_entry(e, str))
		return e;

	return NULL;
}


void update_ext(struct extension *neww)
{
	struct extension *repl;
	struct list *r;
	struct list_head *lr;
	if (!neww->ext[0] || !neww->ct[0]) return;
	foreach(struct list, r, lr, extensions.list_entry) {
		repl = get_struct(r, struct extension, head);
		if (!strcmp(cast_const_char repl->ext, cast_const_char neww->ext)
		 && !strcmp(cast_const_char repl->ct, cast_const_char neww->ct)) {
			del_from_list(&repl->head);
			add_to_list(extensions.list_entry, &repl->head);
			return;
		}
	}
	repl = mem_calloc(sizeof(struct extension));
	repl->ext = stracpy(neww->ext);
	repl->ct = stracpy(neww->ct);
	repl->head.type = 0;
	add_to_list(extensions.list_entry, &repl->head);
}

void update_prog(struct list_head *l, unsigned char *p, int s)
{
	struct protocol_program *repl;
	struct list_head *lrepl;
	foreach(struct protocol_program, repl, lrepl, *l) if (repl->system == s) {
		mem_free(repl->prog);
		goto ss;
	}
	repl = mem_alloc(sizeof(struct protocol_program));
	add_to_list(*l, repl);
	repl->system = s;
ss:
	repl->prog = mem_alloc(MAX_STR_LEN);
	safe_strncpy(repl->prog, p, MAX_STR_LEN);
}

unsigned char *get_prog(struct list_head *l)
{
	struct protocol_program *repl;
	struct list_head *lrepl;
	foreach(struct protocol_program, repl, lrepl, *l) if (repl->system == SYSTEM_ID) return repl->prog;
	update_prog(l, cast_uchar "", SYSTEM_ID);
	foreach(struct protocol_program, repl, lrepl, *l) if (repl->system == SYSTEM_ID) return repl->prog;
	internal_error("get_prog: program was not added");
	return cast_uchar "";;
}


/* creates default extensions if extension list is empty */
void create_initial_extensions(void)
{
	struct extension ext;

	if (!list_empty(extensions.list_entry)) return;

	/* here you can add any default extension you want */
	ext.ext = cast_uchar "xpm", ext.ct=cast_uchar "image/x-xpixmap", update_ext(&ext);
	ext.ext = cast_uchar "xls", ext.ct=cast_uchar "application/excel", update_ext(&ext);
	ext.ext = cast_uchar "xbm", ext.ct=cast_uchar "image/x-xbitmap", update_ext(&ext);
	ext.ext = cast_uchar "webp", ext.ct=cast_uchar "image/webp", update_ext(&ext);
	ext.ext = cast_uchar "wav", ext.ct=cast_uchar "audio/x-wav", update_ext(&ext);
	ext.ext = cast_uchar "tiff,tif", ext.ct=cast_uchar "image/tiff", update_ext(&ext);
	ext.ext = cast_uchar "tga", ext.ct=cast_uchar "image/targa", update_ext(&ext);
	ext.ext = cast_uchar "sxw", ext.ct=cast_uchar "application/x-openoffice", update_ext(&ext);
	ext.ext = cast_uchar "swf", ext.ct=cast_uchar "application/x-shockwave-flash", update_ext(&ext);
	ext.ext = cast_uchar "svg", ext.ct=cast_uchar "image/svg+xml", update_ext(&ext);
	ext.ext = cast_uchar "sch", ext.ct=cast_uchar "application/gschem", update_ext(&ext);
	ext.ext = cast_uchar "rtf", ext.ct=cast_uchar "application/rtf", update_ext(&ext);
	ext.ext = cast_uchar "ra,rm,ram", ext.ct=cast_uchar "audio/x-pn-realaudio", update_ext(&ext);
	ext.ext = cast_uchar "qt,mov", ext.ct=cast_uchar "video/quicktime", update_ext(&ext);
	ext.ext = cast_uchar "ps,eps,ai", ext.ct=cast_uchar "application/postscript", update_ext(&ext);
	ext.ext = cast_uchar "ppt", ext.ct=cast_uchar "application/powerpoint", update_ext(&ext);
	ext.ext = cast_uchar "ppm", ext.ct=cast_uchar "image/x-portable-pixmap", update_ext(&ext);
	ext.ext = cast_uchar "pnm", ext.ct=cast_uchar "image/x-portable-anymap", update_ext(&ext);
	ext.ext = cast_uchar "png", ext.ct=cast_uchar "image/png", update_ext(&ext);
	ext.ext = cast_uchar "pgp", ext.ct=cast_uchar "application/pgp-signature", update_ext(&ext);
	ext.ext = cast_uchar "pgm", ext.ct=cast_uchar "image/x-portable-graymap", update_ext(&ext);
	ext.ext = cast_uchar "pdf", ext.ct=cast_uchar "application/pdf", update_ext(&ext);
	ext.ext = cast_uchar "pcb", ext.ct=cast_uchar "application/pcb", update_ext(&ext);
	ext.ext = cast_uchar "pbm", ext.ct=cast_uchar "image/x-portable-bitmap", update_ext(&ext);
	ext.ext = cast_uchar "mpeg,mpg,mpe", ext.ct=cast_uchar "video/mpeg", update_ext(&ext);
	ext.ext = cast_uchar "mp3", ext.ct=cast_uchar "audio/mpeg", update_ext(&ext);
	ext.ext = cast_uchar "mid,midi", ext.ct=cast_uchar "audio/midi", update_ext(&ext);
	ext.ext = cast_uchar "jpg,jpeg,jpe", ext.ct=cast_uchar "image/jpeg", update_ext(&ext);
	ext.ext = cast_uchar "grb", ext.ct=cast_uchar "application/gerber", update_ext(&ext);
	ext.ext = cast_uchar "gl", ext.ct=cast_uchar "video/gl", update_ext(&ext);
	ext.ext = cast_uchar "gif", ext.ct=cast_uchar "image/gif", update_ext(&ext);
	ext.ext = cast_uchar "gbr", ext.ct=cast_uchar "application/gerber", update_ext(&ext);
	ext.ext = cast_uchar "g", ext.ct=cast_uchar "application/brlcad", update_ext(&ext);
	ext.ext = cast_uchar "fli", ext.ct=cast_uchar "video/fli", update_ext(&ext);
	ext.ext = cast_uchar "dxf", ext.ct=cast_uchar "application/dxf", update_ext(&ext);
	ext.ext = cast_uchar "dvi", ext.ct=cast_uchar "application/x-dvi", update_ext(&ext);
	ext.ext = cast_uchar "dl", ext.ct=cast_uchar "video/dl", update_ext(&ext);
	ext.ext = cast_uchar "deb", ext.ct=cast_uchar "application/x-debian-package", update_ext(&ext);
	ext.ext = cast_uchar "avif", ext.ct=cast_uchar "image/avif", update_ext(&ext);
	ext.ext = cast_uchar "avi", ext.ct=cast_uchar "video/x-msvideo", update_ext(&ext);
	ext.ext = cast_uchar "au,snd", ext.ct=cast_uchar "audio/basic", update_ext(&ext);
	ext.ext = cast_uchar "aif,aiff,aifc", ext.ct=cast_uchar "audio/x-aiff", update_ext(&ext);
}

/* --------------------------- PROG -----------------------------*/


struct list_head mailto_prog = { &mailto_prog, &mailto_prog };
struct list_head telnet_prog = { &telnet_prog, &telnet_prog };
struct list_head tn3270_prog = { &tn3270_prog, &tn3270_prog };
struct list_head mms_prog = { &mms_prog, &mms_prog };
struct list_head magnet_prog = { &magnet_prog, &magnet_prog };
struct list_head gopher_prog = { &gopher_prog, &gopher_prog };


static int is_in_list(unsigned char *list, unsigned char *str, int l)
{
	unsigned char *l2, *l3;
	if (!l) return 0;
	rep:
	while (*list && *list <= ' ') list++;
	if (!*list) return 0;
	for (l2 = list; *l2 && *l2 != ','; l2++)
		;
	for (l3 = l2 - 1; l3 >= list && *l3 <= ' '; l3--)
		;
	l3++;
	if (l3 - list == l && !casecmp(str, list, l)) return 1;
	list = l2;
	if (*list == ',') list++;
	goto rep;
}

static unsigned char *canonical_compressed_ext(unsigned char *ext, unsigned char *ext_end)
{
	size_t len;
	if (!ext_end) ext_end = cast_uchar strchr(cast_const_char ext, 0);
	len = ext_end - ext;
	if (len == 3 && !casecmp(ext, cast_uchar "tgz", 3)) return cast_uchar "gz";
	if (len == 3 && !casecmp(ext, cast_uchar "tbz", 3)) return cast_uchar "bz2";
	if (len == 3 && !casecmp(ext, cast_uchar "txz", 3)) return cast_uchar "xz";
	if (len == 6 && !casecmp(ext, cast_uchar "tar-gz", 3)) return cast_uchar "gz";
	if (len == 7 && !casecmp(ext, cast_uchar "tar-bz2", 3)) return cast_uchar "bz2";
	if (len == 6 && !casecmp(ext, cast_uchar "tar-xz", 3)) return cast_uchar "xz";
	return NULL;
}

unsigned char *get_compress_by_extension(unsigned char *ext, unsigned char *ext_end)
{
	size_t len;
	unsigned char *x;
	if ((x = canonical_compressed_ext(ext, ext_end))) {
		ext = x;
		ext_end = cast_uchar strchr(cast_const_char x, 0);
	}
	len = ext_end - ext;
	if (len == 1 && !casecmp(ext, cast_uchar "z", 1)) return cast_uchar "compress";
	if (len == 2 && !casecmp(ext, cast_uchar "gz", 2)) return cast_uchar "gzip";
	if (len == 2 && !casecmp(ext, cast_uchar "br", 2)) return cast_uchar "br";
	if (len == 3 && !casecmp(ext, cast_uchar "zst", 3)) return cast_uchar "zstd";
	if (len == 3 && !casecmp(ext, cast_uchar "bz2", 3)) return cast_uchar "bzip2";
	if (len == 4 && !casecmp(ext, cast_uchar "lzma", 4)) return cast_uchar "lzma";
	if (len == 2 && !casecmp(ext, cast_uchar "xz", 2)) return cast_uchar "lzma2";
	if (len == 2 && !casecmp(ext, cast_uchar "lz", 2)) return cast_uchar "lzip";
	return NULL;
}

unsigned char *get_content_type_by_extension(unsigned char *url)
{
	struct list *l;
	struct list_head *ll;
	unsigned char *ct, *eod, *ext, *exxt;
	int extl, el;
	ext = NULL, extl = 0;
	if (!(ct = get_url_data(url))) ct = url;
	for (eod = ct; *eod && !end_of_dir(url, *eod); eod++)
		;
	for (; ct < eod; ct++)
		if (*ct == '.') {
			if (ext) {
				if (get_compress_by_extension(ct + 1, eod))
					break;
			}
			ext = ct + 1;
		} else if (dir_sep(*ct)) {
			ext = NULL;
		}
	if (ext) while (ext[extl] && ext[extl] != '.' && !dir_sep(ext[extl]) && !end_of_dir(url, ext[extl])) extl++;
	if ((extl == 3 && !casecmp(ext, cast_uchar "htm", 3)) ||
	    (extl == 4 && !casecmp(ext, cast_uchar "html", 4)) ||
	    (extl == 5 && !casecmp(ext, cast_uchar "xhtml", 5))) return stracpy(cast_uchar "text/html");
	foreach(struct list, l, ll, extensions.list_entry) {
		struct extension *e = get_struct(l, struct extension, head);
		unsigned char *fname = NULL;
		if (!(ct = get_url_data(url))) ct = url;
		for (; *ct && !end_of_dir(url, *ct); ct++)
			if (dir_sep(*ct)) fname = ct + 1;
		if (!fname) {
			if (is_in_list(e->ext, ext, extl)) return stracpy(e->ct);
		} else {
			int fnlen = 0;
			int x;
			while (fname[fnlen] && !end_of_dir(url, fname[fnlen])) fnlen++;
			for (x = 0; x < fnlen; x++) if (fname[x] == '.') if (is_in_list(e->ext, fname + x + 1, fnlen - x - 1)) return stracpy(e->ct);
		}
	}

	if ((extl == 3 && !casecmp(ext, cast_uchar "jpg", 3)) ||
	    (extl == 4 && !casecmp(ext, cast_uchar "pjpg", 4))||
	    (extl == 4 && !casecmp(ext, cast_uchar "jpeg", 4))||
	    (extl == 5 && !casecmp(ext, cast_uchar "pjpeg", 5))) return stracpy(cast_uchar "image/jpeg");
	if ((extl == 3 && !casecmp(ext, cast_uchar "png", 3))) return stracpy(cast_uchar "image/png");
	if ((extl == 3 && !casecmp(ext, cast_uchar "gif", 3))) return stracpy(cast_uchar "image/gif");
	if ((extl == 3 && !casecmp(ext, cast_uchar "xbm", 3))) return stracpy(cast_uchar "image/x-xbitmap");
	if ((extl == 3 && !casecmp(ext, cast_uchar "tif", 3)) ||
	    (extl == 4 && !casecmp(ext, cast_uchar "tiff", 4))) return stracpy(cast_uchar "image/tiff");
	if ((extl == 3 && !casecmp(ext, cast_uchar "svg", 3))) return stracpy(cast_uchar "image/svg+xml");
	if ((extl == 4 && !casecmp(ext, cast_uchar "webp", 4))) return stracpy(cast_uchar "image/webp");
	if ((extl == 4 && !casecmp(ext, cast_uchar "avif", 4))) return stracpy(cast_uchar "image/avif");
	exxt = init_str(); el = 0;
	add_to_str(&exxt, &el, cast_uchar "application/x-");
	add_bytes_to_str(&exxt, &el, ext, extl);
	foreach(struct list, l, ll, assoc.list_entry) {
		struct assoc *a = get_struct(l, struct assoc, head);
		if (is_in_list(a->ct, exxt, el))
			return exxt;
	}
	mem_free(exxt);
	return NULL;
}

static unsigned char *get_content_type_by_header_and_extension(unsigned char *head, unsigned char *url)
{
	unsigned char *ct, *file;
	ct = get_content_type_by_extension(url);
	if (ct) return ct;
	file = get_filename_from_header(head);
	if (file) {
		ct = get_content_type_by_extension(file);
		mem_free(file);
		if (ct) return ct;
	}
	return NULL;
}

static unsigned char *get_extension_by_content_type(unsigned char *ct)
{
	struct list *l;
	struct list_head *ll;
	unsigned char *x, *y;
	if (is_html_type(ct)) return stracpy(cast_uchar "html");
	foreach(struct list, l, ll, extensions.list_entry) {
		struct extension *e = get_struct(l, struct extension, head);
		if (!casestrcmp(e->ct, ct)) {
			x = stracpy(e->ext);
			if ((y = cast_uchar strchr(cast_const_char x, ','))) *y = 0;
			return x;
		}
	}
	if (!casestrcmp(ct, cast_uchar "image/jpeg") ||
	    !casestrcmp(ct, cast_uchar "image/jpg") ||
	    !casestrcmp(ct, cast_uchar "image/jpe") ||
	    !casestrcmp(ct, cast_uchar "image/pjpe") ||
	    !casestrcmp(ct, cast_uchar "image/pjpeg") ||
	    !casestrcmp(ct, cast_uchar "image/pjpg"))
		return stracpy(cast_uchar "jpg");
	if (!casestrcmp(ct, cast_uchar "image/png") ||
	    !casestrcmp(ct, cast_uchar "image/x-png"))
		return stracpy(cast_uchar "png");
	if (!casestrcmp(ct, cast_uchar "image/gif"))
		return stracpy(cast_uchar "gif");
	if (!casestrcmp(ct, cast_uchar "image/x-bitmap"))
		return stracpy(cast_uchar "xbm");
	if (!casestrcmp(ct, cast_uchar "image/tiff") ||
	    !casestrcmp(ct, cast_uchar "image/tif"))
		return stracpy(cast_uchar "tiff");
	if (!casestrcmp(ct, cast_uchar "image/svg") ||
	    !casestrcmp(ct, cast_uchar "image/svg+xml"))
		return stracpy(cast_uchar "svg");
	if (!casestrcmp(ct, cast_uchar "image/webp"))
		return stracpy(cast_uchar "webp");
	if (!casestrcmp(ct, cast_uchar "image/avif"))
		return stracpy(cast_uchar "avif");
	if (!cmpbeg(ct, cast_uchar "application/x-")) {
		x = ct + strlen("application/x-");
		if (casestrcmp(x, cast_uchar "z") &&
		    casestrcmp(x, cast_uchar "gz") &&
		    casestrcmp(x, cast_uchar "gzip") &&
		    casestrcmp(x, cast_uchar "br") &&
		    casestrcmp(x, cast_uchar "zst") &&
		    casestrcmp(x, cast_uchar "bz2") &&
		    casestrcmp(x, cast_uchar "bzip2") &&
		    casestrcmp(x, cast_uchar "lzma") &&
		    casestrcmp(x, cast_uchar "lzma2") &&
		    casestrcmp(x, cast_uchar "xz") &&
		    casestrcmp(x, cast_uchar "lz") &&
		    !strchr(cast_const_char x, '-') &&
		    strlen(cast_const_char x) <= 4) {
			return stracpy(x);
		}
	}
	return NULL;
}

static unsigned char *get_content_encoding_from_content_type(unsigned char *ct)
{
	if (!casestrcmp(ct, cast_uchar "application/x-gzip") ||
	    !casestrcmp(ct, cast_uchar "application/x-tgz") ||
	    !casestrcmp(ct, cast_uchar "application/x-gtar")) return cast_uchar "gzip";
	if (!casestrcmp(ct, cast_uchar "application/x-br")) return cast_uchar "br";
	if (!casestrcmp(ct, cast_uchar "application/x-zstd") ||
	    !casestrcmp(ct, cast_uchar "application/zstd")) return cast_uchar "zstd";
	if (!casestrcmp(ct, cast_uchar "application/x-bzip2") ||
	    !casestrcmp(ct, cast_uchar "application/x-bzip")) return cast_uchar "bzip2";
	if (!casestrcmp(ct, cast_uchar "application/x-lzma")) return cast_uchar "lzma";
	if (!casestrcmp(ct, cast_uchar "application/x-lzma2") ||
	    !casestrcmp(ct, cast_uchar "application/x-xz")) return cast_uchar "lzma2";
	if (!casestrcmp(ct, cast_uchar "application/x-lz") ||
	    !casestrcmp(ct, cast_uchar "application/x-lzip")) return cast_uchar "lzip";
	return NULL;
}

unsigned char *get_content_type(unsigned char *head, unsigned char *url)
{
	unsigned char *ct;
	int code;
	if ((ct = parse_http_header(head, cast_uchar "Content-Type", NULL))) {
		unsigned char *s;
		if ((s = cast_uchar strchr(cast_const_char ct, ';'))) *s = 0;
		while (*ct && ct[strlen(cast_const_char ct) - 1] <= ' ') ct[strlen(cast_const_char ct) - 1] = 0;
		if (*ct == '"' && ct[1] && ct[strlen(cast_const_char ct) - 1] == '"') {
			memmove(ct, ct + 1, strlen(cast_const_char ct));
			ct[strlen(cast_const_char ct) - 1] = 0;
		}
		if (!casestrcmp(ct, cast_uchar "text/plain") ||
		    !casestrcmp(ct, cast_uchar "application/octet-stream") ||
		    !casestrcmp(ct, cast_uchar "application/octetstream") ||
		    !casestrcmp(ct, cast_uchar "application/octet_stream") ||
		    !casestrcmp(ct, cast_uchar "application/binary") ||
		    !casestrcmp(ct, cast_uchar "application/x-www-form-urlencoded") ||
		    get_content_encoding_from_content_type(ct)) {
			unsigned char *ctt;
			if (!get_http_code(head, &code, NULL) && code >= 300)
				goto no_code_by_extension;
			ctt = get_content_type_by_header_and_extension(head, url);
			if (ctt) {
				mem_free(ct);
				return ctt;
			}
		}
no_code_by_extension:
		if (!*ct) mem_free(ct);
		else return ct;
	}
	if (!get_http_code(head, &code, NULL) && code >= 300)
		return stracpy(cast_uchar "text/html");
	ct = get_content_type_by_header_and_extension(head, url);
	if (ct) return ct;
	return !force_html ? stracpy(cast_uchar "text/plain") : stracpy(cast_uchar "text/html");
}

unsigned char *get_content_encoding(unsigned char *head, unsigned char *url, int just_ce)
{
	unsigned char *ce, *ct, *ext, *extd;
	unsigned char *u;
	int code;
	if ((ce = parse_http_header(head, cast_uchar "Content-Encoding", NULL))) return ce;
	if (just_ce) return NULL;
	if ((ct = parse_http_header(head, cast_uchar "Content-Type", NULL))) {
		unsigned char *s;
		if ((s = cast_uchar strchr(cast_const_char ct, ';'))) *s = 0;
		ce = get_content_encoding_from_content_type(ct);
		if (ce) {
			mem_free(ct);
			return stracpy(ce);
		}
		if (is_html_type(ct)) {
			mem_free(ct);
			return NULL;
		}
		mem_free(ct);
	}
	if (!get_http_code(head, &code, NULL) && code >= 300) return NULL;
	if (!(ext = get_url_data(url))) ext = url;
	for (u = ext; *u; u++) if (end_of_dir(url, *u)) goto skip_ext;
	extd = cast_uchar strrchr(cast_const_char ext, '.');
	if (extd) {
		ce = get_compress_by_extension(extd + 1, cast_uchar strchr(cast_const_char(extd + 1), 0));
		if (ce) return stracpy(ce);
	}
	skip_ext:
	if ((ext = get_filename_from_header(head))) {
		extd = cast_uchar strrchr(cast_const_char ext, '.');
		if (extd) {
			ce = get_compress_by_extension(extd + 1, cast_uchar strchr(cast_const_char(extd + 1), 0));
			if (ce) {
				mem_free(ext);
				return stracpy(ce);
			}
		}
		mem_free(ext);
	}
	return NULL;
}

unsigned char *encoding_2_extension(unsigned char *encoding)
{
	if (!casestrcmp(encoding, cast_uchar "gzip") ||
	    !casestrcmp(encoding, cast_uchar "x-gzip")) return cast_uchar "gz";
	if (!casestrcmp(encoding, cast_uchar "compress") ||
	    !casestrcmp(encoding, cast_uchar "x-compress")) return cast_uchar "Z";
	if (!casestrcmp(encoding, cast_uchar "br")) return cast_uchar "br";
	if (!casestrcmp(encoding, cast_uchar "zstd")) return cast_uchar "zst";
	if (!casestrcmp(encoding, cast_uchar "bzip2")) return cast_uchar "bz2";
	if (!casestrcmp(encoding, cast_uchar "lzma")) return cast_uchar "lzma";
	if (!casestrcmp(encoding, cast_uchar "lzma2")) return cast_uchar "xz";
	if (!casestrcmp(encoding, cast_uchar "lzip")) return cast_uchar "lz";
	return NULL;
}

/* returns field with associations */
struct assoc *get_type_assoc(struct terminal *term, unsigned char *type, int *n)
{
	struct assoc *assoc_array;
	struct list *l;
	struct list_head *ll;
	int count=0;
	foreach(struct list, l, ll, assoc.list_entry) {
		struct assoc *a = get_struct(l, struct assoc, head);
		if (a->system == SYSTEM_ID && (term->environment & ENV_XWIN ? a->xwin : a->cons) && is_in_list(a->ct, type, (int)strlen(cast_const_char type))) {
			if (count == MAXINT) overalloc();
			count++;
		}
	}
	*n = count;
	if (!count) return NULL;
	if ((unsigned)count > MAXINT / sizeof(struct assoc)) overalloc();
	assoc_array = mem_alloc(count * sizeof(struct assoc));
	count = 0;
	foreach(struct list, l, ll, assoc.list_entry) {
		struct assoc *a = get_struct(l, struct assoc, head);
		if (a->system == SYSTEM_ID && (term->environment & ENV_XWIN ? a->xwin : a->cons) && is_in_list(a->ct, type, (int)strlen(cast_const_char type))) {
			assoc_array[count++] = *a;
		}
	}
	return assoc_array;
}

int is_html_type(unsigned char *ct)
{
	return	!casestrcmp(ct, cast_uchar "text/html") ||
		!casestrcmp(ct, cast_uchar "text-html") ||
		!casestrcmp(ct, cast_uchar "text/x-server-parsed-html") ||
		!casestrcmp(ct, cast_uchar "text/xml") ||
		!casecmp(ct, cast_uchar "application/xhtml", strlen("application/xhtml"));
}

unsigned char *get_filename_from_header(unsigned char *head)
{
	int extended = 0;
	unsigned char *ct, *x, *y, *codepage;
	int ly;
	int cp_idx;
	if ((ct = parse_http_header(head, cast_uchar "Content-Disposition", NULL))) {
		x = parse_header_param(ct, cast_uchar "filename*", 1);
		if (x) extended = 1;
		else x = parse_header_param(ct, cast_uchar "filename", 1);
		mem_free(ct);
		if (x) {
			if (*x) goto ret_x;
			mem_free(x);
		}
	}
	if ((ct = parse_http_header(head, cast_uchar "Content-Type", NULL))) {
		x = parse_header_param(ct, cast_uchar "name*", 0);
		if (x) extended = 1;
		else x = parse_header_param(ct, cast_uchar "name", 0);
		mem_free(ct);
		if (x) {
			if (*x) goto ret_x;
			mem_free(x);
		}
	}
	return NULL;
ret_x:
	codepage = NULL;
	if (extended) {
		unsigned char *ap1, *ap2;
		ap1 = cast_uchar strchr(cast_const_char x, '\'');
		if (!ap1)
			goto no_extended;
		ap2 = cast_uchar strchr(cast_const_char (ap1 + 1), '\'');
		if (ap2) ap2++;
		else ap2 = ap1 + 1;
		codepage = memacpy(x, ap1 - x);
		memmove(x, ap2, strlen(cast_const_char ap2) + 1);
	}

no_extended:
	y = init_str();
	ly = 0;
	add_conv_str(&y, &ly, x, (int)strlen(cast_const_char x), -2);
	mem_free(x);
	x = y;

	cp_idx = -1;
	if (codepage) {
		cp_idx = get_cp_index(codepage);
		mem_free(codepage);
	}
	if (cp_idx < 0) {
		cp_idx = get_cp_index(cast_uchar "iso-8859-1");
		if (cp_idx < 0) cp_idx = 0;
	}
	y = convert(cp_idx, 0, x, NULL);
	mem_free(x);
	x = y;

	for (y = x; *y; y++) if (dir_sep(*y)
#if defined(DOS_FS) || defined(SPAD)
		|| *y == ':'
#endif
		) *y = '-';
	return x;
}

unsigned char *get_filename_from_url(unsigned char *url, unsigned char *head, int tmp)
{
	int ll = 0;
	unsigned char *u, *s, *e, *f, *x, *ww;
	unsigned char *ct, *want_ext;
	if (!casecmp(url, cast_uchar "data:", 5)) {
		url = cast_uchar "data:/data";
	}
	want_ext = stracpy(cast_uchar "");
	f = get_filename_from_header(head);
	if (f) {
		goto no_ct;
	}
	if (!(u = get_url_data(url))) u = url;
	for (e = s = u; *e && !end_of_dir(url, *e); e++) {
		if (dir_sep(*e)) s = e + 1;
	}
	ll = 0;
	f = init_str();
	add_conv_str(&f, &ll, s, (int)(e - s), -2);
	if (!(ct = parse_http_header(head, cast_uchar "Content-Type", NULL))) goto no_ct;
	mem_free(ct);
	ct = get_content_type(head, url);
	if (ct) {
		x = get_extension_by_content_type(ct);
		if (x) {
			add_to_strn(&want_ext, cast_uchar ".");
			add_to_strn(&want_ext, x);
			mem_free(x);
		}
		mem_free(ct);
	}
	no_ct:
	if (!*want_ext) {
		x = cast_uchar strrchr(cast_const_char f, '.');
		if (x) {
			mem_free(want_ext);
			want_ext = stracpy(x);
		}
	}
	ct = get_content_encoding(head, url, 0);
	if (ct) {
		x = encoding_2_extension(ct);
		if (!tmp) {
			unsigned char *ct1;
			ct1 = get_content_encoding(head, url, 1);
			if (ct1) {
				mem_free(ct1);
			} else if (x) {
				unsigned char *w = cast_uchar strrchr(cast_const_char want_ext, '.');
				if (w && (ww = canonical_compressed_ext(w + 1, NULL)) && !casestrcmp(x, ww))
					goto skip_want_ext;
				if (w && !casestrcmp(w + 1, x))
					goto skip_want_ext;
				add_to_strn(&want_ext, cast_uchar ".");
				add_to_strn(&want_ext, x);
				skip_want_ext:;
			}
		} else {
			if (x) {
				if (strlen(cast_const_char x) + 1 < strlen(cast_const_char f) && f[strlen(cast_const_char f) - strlen(cast_const_char x) - 1] == '.' && !casestrcmp(f + strlen(cast_const_char f) - strlen(cast_const_char x), x)) {
					f[strlen(cast_const_char f) - strlen(cast_const_char x) - 1] = 0;
				}
			}
		}
		mem_free(ct);
	}
	if (strlen(cast_const_char want_ext) > strlen(cast_const_char f) || casestrcmp(want_ext, f + strlen(cast_const_char f) - strlen(cast_const_char want_ext))) {
		x = cast_uchar strrchr(cast_const_char f, '.');
		if (x && (ww = canonical_compressed_ext(x + 1, NULL)) && want_ext[0] == '.' && !casestrcmp(want_ext + 1, ww))
			goto skip_tgz_2;
		if (x) *x = 0;
		add_to_strn(&f, want_ext);
		skip_tgz_2:;
	}
	mem_free(want_ext);
	return f;
}

static void free_prog_list(struct list_head *l)
{
	struct list_head *lp;
	struct protocol_program *p;
	foreach(struct protocol_program, p, lp, *l)
		mem_free(p->prog);
	free_list(struct protocol_program, *l);
}

void free_types(void)
{
	struct list *l;
	struct list_head *ll;
	foreach(struct list, l, ll, assoc.list_entry) {
		struct assoc *a = get_struct(l, struct assoc, head);
		mem_free(a->ct);
		mem_free(a->prog);
		mem_free(a->label);
		ll = ll->prev;
		del_from_list(&a->head);
		mem_free(a);
	}
	foreach(struct list, l, ll, extensions.list_entry) {
		struct extension *e = get_struct(l, struct extension, head);
		mem_free(e->ext);
		mem_free(e->ct);
		ll = ll->prev;
		del_from_list(&e->head);
		mem_free(e);
	}
	free_prog_list(&mailto_prog);
	free_prog_list(&telnet_prog);
	free_prog_list(&tn3270_prog);
	free_prog_list(&mms_prog);
	free_prog_list(&magnet_prog);
	free_prog_list(&gopher_prog);

	free_history(ext_search_history);
	free_history(assoc_search_history);
}
