#include "links.h"

struct list_head downloads = {&downloads, &downloads};

int are_there_downloads()
{
	int d = 0;
	struct download *down;
	foreach(down, downloads) if (!down->prog) d = 1;
	return d;
}

struct list_head sessions = {&sessions, &sessions};

int session_id = 1;

struct strerror_val {
	struct strerror_val *next;
	struct strerror_val *prev;
	unsigned char msg[1];
};

struct list_head strerror_buf = { &strerror_buf, &strerror_buf };

void free_strerror_buf()
{
	free_list(strerror_buf);
}

unsigned char *get_err_msg(int state)
{
	unsigned char *e;
	struct strerror_val *s;
	if (state <= S_OK || state >= S_WAIT) {
		int i;
		for (i = 0; msg_dsc[i].msg; i++)
			if (msg_dsc[i].n == state) return msg_dsc[i].msg;
		unk:
		return TEXT(T_UNKNOWN_ERROR);
	}
	if (!(e = strerror(-state)) || !*e) goto unk;
	foreach(s, strerror_buf) if (!strcmp(s->msg, e)) return s->msg;
	if (!(s = mem_alloc(sizeof(struct strerror_val) + strlen(e) + 1))) goto unk;
	strcpy(s->msg, e);
	add_to_list(strerror_buf, s);
	return s->msg;
}

void add_xnum_to_str(unsigned char **s, int *l, int n)
{
	unsigned char suff = 0;
	int d = -1;
	if (n >= 1000000) suff = 'M', d = (n / 100000) % 10, n /= 1000000;
	else if (n >= 1000) suff = 'k', d = (n / 100) % 10, n /= 1000;
	add_num_to_str(s, l, n);
	if (n < 10 && d != -1) add_chr_to_str(s, l, '.'), add_num_to_str(s, l, d);
	add_chr_to_str(s, l, ' ');
	if (suff) add_chr_to_str(s, l, suff);
	add_chr_to_str(s, l, 'B');
}

void add_time_to_str(unsigned char **s, int *l, ttime t)
{
	unsigned char q[64];
	t /= 1000;
	t &= 0xffffffff;
	if (t >= 86400) sprintf(q, "%dd ", (int)(t / 86400)), add_to_str(s, l, q), t %= 86400;
	if (t >= 3600) sprintf(q, "%d:%02d", (int)(t / 3600), (int)(t / 60 % 60)), add_to_str(s, l, q);
	else sprintf(q, "%d", (int)(t / 60)), add_to_str(s, l, q);
	sprintf(q, ":%02d", (int)(t % 60)), add_to_str(s, l, q);
}

unsigned char *get_stat_msg(struct status *stat, struct terminal *term)
{
	if (stat->state == S_TRANS && stat->prg->elapsed / 100) {
		unsigned char *m = init_str();
		int l = 0;
		add_to_str(&m, &l, _(TEXT(T_RECEIVED), term));
		add_to_str(&m, &l, " ");
		add_xnum_to_str(&m, &l, stat->prg->pos);
		if (stat->prg->size >= 0)
			add_to_str(&m, &l, " "), add_to_str(&m, &l, _(TEXT(T_OF), term)), add_to_str(&m, &l, " "), add_xnum_to_str(&m, &l, stat->prg->size);
		add_to_str(&m, &l, ", ");
		if (stat->prg->elapsed >= CURRENT_SPD_AFTER * SPD_DISP_TIME)
			add_to_str(&m, &l, _(TEXT(T_AVG), term)), add_to_str(&m, &l, " ");
		add_xnum_to_str(&m, &l, (longlong)stat->prg->loaded * 10 / (stat->prg->elapsed / 100));
		add_to_str(&m, &l, "/s");
		if (stat->prg->elapsed >= CURRENT_SPD_AFTER * SPD_DISP_TIME) 
			add_to_str(&m, &l, ", "), add_to_str(&m, &l, _(TEXT(T_CUR), term)), add_to_str(&m, &l, " "),
			add_xnum_to_str(&m, &l, stat->prg->cur_loaded / (CURRENT_SPD_SEC * SPD_DISP_TIME / 1000)),
			add_to_str(&m, &l, "/s");
		return m;
	}
	return stracpy(_(get_err_msg(stat->state), term));
}

void print_screen_status(struct session *ses)
{
	struct terminal *term = ses->term;
	struct status *stat = NULL;
	unsigned char *m;
	fill_area(term, 0, 0, term->x, 1, COLOR_TITLE_BG);
	fill_area(term, 0, term->y - 1, term->x, 1, COLOR_STATUS_BG);
	if (ses->wtd) stat = &ses->loading;
	else if (!list_empty(ses->history)) stat = &cur_loc(ses)->stat;
	if (stat && stat->state == S_OK) {
		struct file_to_load *ftl;
		foreach(ftl, ses->more_files) {
			if (ftl->req_sent && ftl->stat.state >= 0) {
				stat = &ftl->stat;
				break;
			}
		}
	}
	if (stat) {
		if (stat->state == S_OK) if ((m = print_current_link(ses))) goto p;
		if ((m = get_stat_msg(stat, term))) {
			p:
			print_text(term, 0, term->y - 1, strlen(m), m, COLOR_STATUS);
			mem_free(m);
		}
		if ((m = print_current_title(ses))) {
			int p = term->x - 1 - strlen(m);
			if (p < 0) p = 0;
			print_text(term, p, 0, strlen(m), m, COLOR_TITLE);
			/*set_window_title(0,m);*/
			/*set_terminal_title(term, m);*/
			mem_free(m);
		}
		if ((m = stracpy("Links"))) {
			if (ses->screen && ses->screen->f_data && ses->screen->f_data->title && ses->screen->f_data->title[0]) add_to_strn(&m, " - "), add_to_strn(&m, ses->screen->f_data->title);
			set_terminal_title(term, m);
			mem_free(m);
		}
	}
	redraw_from_window(ses->win);
}

void print_error_dialog(struct session *ses, struct status *stat, unsigned char *title)
{
	unsigned char *t = get_err_msg(stat->state);
	if (!t) return;
	msg_box(ses->term, NULL, title, AL_CENTER, t, ses, 1, TEXT(T_CANCEL), NULL, B_ENTER | B_ESC/*, _("Retry"), NULL, 0 !!! FIXME: retry */);
}

void free_wtd(struct session *ses)
{
	if (!ses->wtd) {
		internal("no WTD");
		return;
	}
	if (ses->goto_position) mem_free(ses->goto_position), ses->goto_position = NULL;
	mem_free(ses->loading_url);
	ses->loading_url = NULL;
	ses->wtd = WTD_NO;
}

void abort_files_load(struct session *ses)
{
	struct file_to_load *ftl;
	int q;
	do {
		q = 0;
		foreach(ftl, ses->more_files) {
			if (ftl->stat.state >= 0 && ftl->req_sent) {
				q = 1;
				change_connection(&ftl->stat, NULL, PRI_CANCEL);
			}
		}
	} while (q);
}

void free_files(struct session *ses)
{
	struct file_to_load *ftl;
	abort_files_load(ses);
	foreach(ftl, ses->more_files) {
		if (ftl->ce) ftl->ce->refcount--;
		mem_free(ftl->url);
	}
	free_list(ses->more_files);
}

void destroy_location(struct location *loc)
{
	struct frame *frame;
	del_from_list(loc);
	foreach(frame, loc->frames) {
		destroy_vs(&frame->vs);
		mem_free(frame->name);
	}
	free_list(loc->frames);
	destroy_vs(&loc->vs);
	mem_free(loc);
}

void ses_forward(struct session *ses)
{
	struct location *l;
	int len;
	free_files(ses);
	if (!list_empty(ses->history)) {
		struct frame *frm;
		l = cur_loc(ses);
		foreach(frm, l->frames) frm->vs.f = NULL;
		l->vs.f = NULL;
	}
	if (ses->search_word) mem_free(ses->search_word), ses->search_word = NULL;
	x:
	len = strlen(ses->loading_url);
	if (!list_empty(ses->history) && len < strlen(cur_loc(ses)->vs.url))
		len = strlen(cur_loc(ses)->vs.url);
	if (!(l = mem_alloc(sizeof(struct location) + len + 1))) return;
	memset(l, 0, sizeof(struct location));
	memcpy(&l->stat, &ses->loading, sizeof(struct status));
	if (ses->wtd_target && *ses->wtd_target) {
		struct frame *frm;
		if (list_empty(ses->history)) {
			internal("no history");
			return;
		}
		copy_location(l, cur_loc(ses));
		add_to_list(ses->history, l);
		frm = ses_change_frame_url(ses, ses->wtd_target, ses->loading_url);
		if (!frm) {
			destroy_location(l);
			ses->wtd_target = NULL;
			goto x;
		}
		destroy_vs(&frm->vs);
		init_vs(&frm->vs, ses->loading_url);
		if (ses->goto_position) {
			if (frm->vs.goto_position) mem_free(frm->vs.goto_position);
			frm->vs.goto_position = ses->goto_position;
			ses->goto_position = NULL;
		}
		/*request_additional_loading_file(ses, ses->loading_url, &ses->loading, PRI_FRAME);*/
	} else {
		init_list(l->frames);
		init_vs(&l->vs, ses->loading_url);
		add_to_list(ses->history, l);
		if (ses->goto_position) {
			l->vs.goto_position = ses->goto_position;
			ses->goto_position = NULL;
		}
	}
}

void ses_imgmap(struct session *ses)
{
	struct cache_entry *ce;
	struct fragment *fr;
	struct memory_list *ml;
	struct menu_item *menu;
	if (find_in_cache(ses->loading_url, &ce) || !ce) {
		internal("can't find cache entry");
		return;
	}
	fr = ce->frag.next;
	if ((void *)fr == &ce->frag) return;
	if (get_image_map(ce->head, fr->data, fr->data + fr->length, ses->goto_position, &menu, &ml, ses->imgmap_href_base, ses->imgmap_target_base, ses->term->spec->charset, ses->ds.assume_cp, ses->ds.hard_assume))
		return;
	add_empty_window(ses->term, (void (*)(void *))freeml, ml);
	do_menu(ses->term, menu, ses);
}

void map_selected(struct terminal *term, struct link_def *ld, struct session *ses)
{
	goto_url_f(ses, ld->link, ld->target);
}

void ses_back(struct session *ses)
{
	struct location *loc;
	free_files(ses);
	loc = ses->history.next;
	if (ses->search_word) mem_free(ses->search_word), ses->search_word = NULL;
	if ((void *)loc == &ses->history) return;
	destroy_location(loc);
	loc = ses->history.next;
	if ((void *)loc == &ses->history) return;
	if (!strcmp(loc->vs.url, ses->loading_url)) return;
	destroy_location(loc);
	ses_forward(ses);
}

void end_load(struct status *, struct session *);
void doc_end_load(struct status *, struct session *);
void file_end_load(struct status *, struct file_to_load *);
void abort_loading(struct session *);
void abort_preloading(struct session *);

struct session *get_download_ses(struct download *down)
{
	struct session *ses;
	foreach(ses, sessions) if (ses == down->ses) return ses;
	if (!list_empty(sessions)) return sessions.next;
	return NULL;
}

void abort_download(struct download *down)
{
	if (down->win) delete_window(down->win);
	if (down->ask) delete_window(down->ask);
	if (down->stat.state >= 0) change_connection(&down->stat, NULL, PRI_CANCEL);
	mem_free(down->url);
	if (down->handle != -1) close(down->handle);
	if (down->prog) {
		unlink(down->file);
		mem_free(down->prog);
	}
	mem_free(down->file);
	del_from_list(down);
	mem_free(down);
}

void kill_downloads_to_file(unsigned char *file)
{
	struct download *down;
	foreach(down, downloads) if (!strcmp(down->file, file)) down = down->prev, abort_download(down->next);
}

void undisplay_download(struct download *down)
{
	if (down->win) delete_window(down->win);
}

int dlg_abort_download(struct dialog_data *dlg, struct dialog_item_data *di)
{
	register_bottom_half((void (*)(void *))abort_download, dlg->dlg->udata);
	return 0;
}

int dlg_undisplay_download(struct dialog_data *dlg, struct dialog_item_data *di)
{
	register_bottom_half((void (*)(void *))undisplay_download, dlg->dlg->udata);
	return 0;
}

void download_abort_function(struct dialog_data *dlg)
{
	struct download *down = dlg->dlg->udata;
	down->win = NULL;
}

void download_window_function(struct dialog_data *dlg)
{
	struct download *down = dlg->dlg->udata;
	struct terminal *term = dlg->win->term;
	int max = 0, min = 0;
	int w, x, y;
	int t = 0;
	unsigned char *m, *u;
	struct status *stat = &down->stat;
	redraw_below_window(dlg->win);
	down->win = dlg->win;
	if (stat->state == S_TRANS && stat->prg->elapsed / 100) {
		int l = 0;
		m = init_str();
		t = 1;
		add_to_str(&m, &l, _(TEXT(T_RECEIVED), term));
		add_to_str(&m, &l, " ");
		add_xnum_to_str(&m, &l, stat->prg->pos);
		if (stat->prg->size >= 0)
			add_to_str(&m, &l, " "), add_to_str(&m, &l, _(TEXT(T_OF),term)), add_to_str(&m, &l, " "), add_xnum_to_str(&m, &l, stat->prg->size), add_to_str(&m, &l, " ");
		add_to_str(&m, &l, "\n");
		if (stat->prg->elapsed >= CURRENT_SPD_AFTER * SPD_DISP_TIME)
			add_to_str(&m, &l, _(TEXT(T_AVERAGE_SPEED), term));
		else add_to_str(&m, &l, _(TEXT(T_SPEED), term));
		add_to_str(&m, &l, " ");
		add_xnum_to_str(&m, &l, (longlong)stat->prg->loaded * 10 / (stat->prg->elapsed / 100));
		add_to_str(&m, &l, "/s");
		if (stat->prg->elapsed >= CURRENT_SPD_AFTER * SPD_DISP_TIME) 
			add_to_str(&m, &l, ", "), add_to_str(&m, &l, _(TEXT(T_CURRENT_SPEED), term)), add_to_str(&m, &l, " "),
			add_xnum_to_str(&m, &l, stat->prg->cur_loaded / (CURRENT_SPD_SEC * SPD_DISP_TIME / 1000)),
			add_to_str(&m, &l, "/s");
		add_to_str(&m, &l, "\n");
		add_to_str(&m, &l, _(TEXT(T_ELAPSED_TIME), term));
		add_to_str(&m, &l, " ");
		add_time_to_str(&m, &l, stat->prg->elapsed);
		if (stat->prg->size >= 0 && stat->prg->loaded > 0) {
			add_to_str(&m, &l, ", ");
			add_to_str(&m, &l, _(TEXT(T_ESTIMATED_TIME), term));
			add_to_str(&m, &l, " ");
			/*add_time_to_str(&m, &l, stat->prg->elapsed / 1000 * stat->prg->size / stat->prg->loaded * 1000 - stat->prg->elapsed);*/
			add_time_to_str(&m, &l, (stat->prg->size - stat->prg->pos) / ((longlong)stat->prg->loaded * 10 / (stat->prg->elapsed / 100)) * 1000);
		}
	} else m = stracpy(_(get_err_msg(stat->state), term));
	if (!m) return;
	u = stracpy(down->url);
	if (strchr(u, POST_CHAR)) *strchr(u, POST_CHAR) = 0;
	max_text_width(term, u, &max);
	min_text_width(term, u, &min);
	max_text_width(term, m, &max);
	min_text_width(term, m, &min);
	max_buttons_width(term, dlg->items, dlg->n, &max);
	min_buttons_width(term, dlg->items, dlg->n, &min);
	w = dlg->win->term->x * 9 / 10 - 2 * DIALOG_LB;
	if (w < min) w = min;
	if (w > dlg->win->term->x - 2 * DIALOG_LB) w = dlg->win->term->x - 2 * DIALOG_LB;
	if (t && stat->prg->size >= 0) {
		if (w < DOWN_DLG_MIN) w = DOWN_DLG_MIN;
	} else {
		if (w > max) w = max;
	}
	if (w < 1) w = 1;
	y = 0;
	dlg_format_text(NULL, term, u, 0, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y++;
	if (t && stat->prg->size >= 0) y += 2;
	dlg_format_text(NULL, term, m, 0, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y++;
	dlg_format_buttons(NULL, term, dlg->items, dlg->n, 0, &y, w, NULL, AL_CENTER);
	dlg->xw = w + 2 * DIALOG_LB;
	dlg->yw = y + 2 * DIALOG_TB;
	center_dlg(dlg);
	draw_dlg(dlg);
	y = dlg->y + DIALOG_TB + 1;
	x = dlg->x + DIALOG_LB;
	dlg_format_text(term, term, u, x, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	if (t && stat->prg->size >= 0) {
		unsigned char q[5];
		int p = w - 6;
		y++;
		set_only_char(term, x, y, '[');
		set_only_char(term, x + w - 5, y, ']');
		fill_area(term, x + 1, y, (int)((longlong)p * (longlong)stat->prg->pos / (longlong)stat->prg->size), 1, COLOR_DIALOG_METER);
		sprintf(q, "%3d%%", (int)((longlong)100 * (longlong)stat->prg->pos / (longlong)stat->prg->size));
		print_text(term, x + w - 4, y, strlen(q), q, COLOR_DIALOG_TEXT);
		y++;
	}
	y++;
	dlg_format_text(term, term, m, x, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	y++;
	dlg_format_buttons(term, term, dlg->items, dlg->n, x, &y, w, NULL, AL_CENTER);
	mem_free(u);
	mem_free(m);
}

void display_download(struct terminal *term, struct download *down, struct session *ses)
{
	struct dialog *dlg;
	struct download *dd;
	foreach(dd, downloads) if (dd == down) goto found;
	return;
	found:
	if (!(dlg = mem_alloc(sizeof(struct dialog) + 3 * sizeof(struct dialog_item)))) return;
	memset(dlg, 0, sizeof(struct dialog) + 3 * sizeof(struct dialog_item));
	undisplay_download(down);
	down->ses = ses;
	dlg->title = TEXT(T_DOWNLOAD);
	dlg->fn = download_window_function;
	dlg->abort = download_abort_function;
	dlg->udata = down;
	dlg->align = AL_CENTER;
	dlg->items[0].type = D_BUTTON;
	dlg->items[0].gid = B_ENTER | B_ESC;
	dlg->items[0].fn = dlg_undisplay_download;
	dlg->items[0].text = TEXT(T_BACKGROUND);
	dlg->items[1].type = D_BUTTON;
	dlg->items[1].gid = 0;
	dlg->items[1].fn = dlg_abort_download;
	dlg->items[1].text = TEXT(T_ABORT);
	dlg->items[2].type = D_END;
	do_dialog(term, dlg, getml(dlg, NULL));
}

time_t parse_http_date(const char *date)
{
	const char *months[12] =
		{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

	time_t t = 0;
	/* Mon, 03 Jan 2000 21:29:33 GMT */
	struct tm tm;
	if (!date || strlen(date) < 28) return 0;
	date += 5;
	tm.tm_mday = (date[0] - '0') * 10 + date[1] - '0';
	date += 3;
	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++)
		if (!strncmp(date, months[tm.tm_mon], 3)) break;
	date += 4;
	tm.tm_year = (date[0] - '0') * 1000 + (date[1] - '0') * 100 + (date[2] - '0') * 10 + date[3] - '0' - 1900;
	date += 5;
	tm.tm_hour = (date[0] - '0') * 10 + date[1] - '0';
	date += 3;
	tm.tm_min = (date[0] - '0') * 10 + date[1] - '0';
	date += 3;
	tm.tm_sec = (date[0] - '0') * 10 + date[1] - '0';
	t = mktime(&tm);
	if (t == (time_t) - 1) return 0;
	else return t;
}

void download_data(struct status *stat, struct download *down)
{
	struct cache_entry *ce;
	struct fragment *frag;
	if (stat->state >= S_WAIT && stat->state < S_TRANS) goto end_store;
	if (!(ce = stat->ce)) goto end_store;
	if (ce->last_modified)
	down->remotetime = parse_http_date(ce->last_modified);
/*	  fprintf(stderr,"\nFEFE date %s\n",ce->last_modified); */
	if (ce->redirect && down->redirect_cnt++ < MAX_REDIRECTS) {
		unsigned char *u, *p;
		if (stat->state >= 0) change_connection(&down->stat, NULL, PRI_CANCEL);
		u = stracpy(ce->redirect);
		if (!http_bugs.bug_302_redirect) if (!ce->redirect_get && (p = strchr(down->url, POST_CHAR))) add_to_strn(&u, p);
		mem_free(down->url);
		down->url = u;
		down->stat.state = S_WAIT_REDIR;
		if (down->win) {
			struct event ev = { EV_REDRAW, 0, 0, 0 };
			ev.x = down->win->term->x;
			ev.y = down->win->term->y;
			down->win->handler(down->win, &ev, 0);
		}
		/*if (!strchr(down->url, POST_CHAR)) {*/
			load_url(down->url, &down->stat, PRI_DOWNLOAD, NC_CACHE);
			return;
		/*} else {
			unsigned char *msg = init_str();
			int l = 0;
			add_bytes_to_str(&msg, &l, down->url, (unsigned char *)strchr(down->url, POST_CHAR) - down->url);
			msg_box(get_download_ses(down)->term, getml(msg, NULL), TEXT(T_WARNING), AL_CENTER | AL_EXTD_TEXT, TEXT(T_DO_YOU_WANT_TO_FOLLOW_REDIRECT_AND_POST_FORM_DATA_TO_URL), "", msg, "?", NULL, down, 3, TEXT(T_YES), down_post_yes, B_ENTER, TEXT(T_NO), down_post_no, 0, TEXT(T_CANCEL), down_post_cancel, B_ESC);
		}*/
	}
	foreach(frag, ce->frag) if (frag->offset <= down->last_pos && frag->offset + frag->length > down->last_pos) {
		int w = write(down->handle, frag->data + down->last_pos - frag->offset, frag->length - (down->last_pos - frag->offset));
		if (w == -1) {
			detach_connection(stat, down->last_pos);
			if (!list_empty(sessions)) {
				unsigned char *msg = stracpy(down->file);
				unsigned char *emsg = stracpy(strerror(errno));
				msg_box(get_download_ses(down)->term, getml(msg, emsg, NULL), TEXT(T_DOWNLOAD_ERROR), AL_CENTER | AL_EXTD_TEXT, TEXT(T_COULD_NOT_WRITE_TO_FILE), " ", msg, ": ", emsg, NULL, NULL, 1, TEXT(T_CANCEL), NULL, B_ENTER | B_ESC);
			}
			abort_download(down);
			return;
		}
		down->last_pos += w;
	}
	detach_connection(stat, down->last_pos);
	end_store:;
	if (stat->state < 0) {
		if (stat->state != S_OK) {
			unsigned char *t = get_err_msg(stat->state);
			if (t) {
				unsigned char *tt = stracpy(down->url);
				if (strchr(tt, POST_CHAR)) *strchr(tt, POST_CHAR) = 0;
				msg_box(get_download_ses(down)->term, getml(tt, NULL), TEXT(T_DOWNLOAD_ERROR), AL_CENTER | AL_EXTD_TEXT, TEXT(T_ERROR_DOWNLOADING), " ", tt, ":\n\n", t, NULL, get_download_ses(down), 1, TEXT(T_CANCEL), NULL, B_ENTER | B_ESC/*, TEXT(T_RETRY), NULL, 0 !!! FIXME: retry */);
			}
		} else {
			if (down->prog) {
				close(down->handle), down->handle = -1;
				exec_on_terminal(get_download_ses(down)->term, down->prog, down->file, !!down->prog_flags);
				mem_free(down->prog), down->prog = NULL;
			} else if (down->remotetime && download_utime) {
				struct utimbuf foo;
				foo.actime = foo.modtime = down->remotetime;
				utime(down->file, &foo);
			}
		}
		abort_download(down);
		return;
	}
	if (down->win) {
		struct event ev = { EV_REDRAW, 0, 0, 0 };
		ev.x = down->win->term->x;
		ev.y = down->win->term->y;
		down->win->handler(down->win, &ev, 0);
	}
}

int create_download_file(struct terminal *term, unsigned char *fi, int safe)
{
	unsigned char *file = fi;
	unsigned char *wd;
	int h;
	int i;
#ifdef NO_FILE_SECURITY
	int sf = 0;
#else
	int sf = safe;
#endif
	wd = get_cwd();
	set_cwd(term->cwd);
	if (file[0] == '~' && dir_sep(file[1])) {
		unsigned char *h = getenv("HOME");
		if (h) {
			int fl = 0;
			if ((file = init_str())) {
				add_to_str(&file, &fl, h);
				add_to_str(&file, &fl, fi + 1);
			}
		}
	}
	h = open(file, O_CREAT|O_WRONLY|O_TRUNC|(sf?O_EXCL:0), sf ? 0600 : 0666);
	if (wd) set_cwd(wd), mem_free(wd);
	if (h == -1) {
		unsigned char *msg = stracpy(file);
		unsigned char *msge = stracpy(strerror(errno));
		msg_box(term, getml(msg, msge, NULL), TEXT(T_DOWNLOAD_ERROR), AL_CENTER | AL_EXTD_TEXT, TEXT(T_COULD_NOT_CREATE_FILE), " ", msg, ": ", msge, NULL, NULL, 1, TEXT(T_CANCEL), NULL, B_ENTER | B_ESC);
		if (file != fi) mem_free(file);
		return -1;
	}
	set_bin(h);
	if (safe) goto x;
	if (strlen(file) > MAX_STR_LEN) memcpy(download_dir, file, MAX_STR_LEN - 1), download_dir[MAX_STR_LEN - 1] = 0;
	else strcpy(download_dir, file);
	for (i = strlen(download_dir) - 1; i >= 0; i--) if (dir_sep(download_dir[i])) {
		download_dir[i + 1] = 0;
		goto x;
	}
	download_dir[0] = 0;
	x:
	if (file != fi) mem_free(file);
	return h;
}

unsigned char *get_temp_name(unsigned char *url)
{
	int l, nl;
	unsigned char *name, *fn, *fnn, *fnnn, *s;
	unsigned char *nm = tempnam(NULL, "links");
	if (!nm) return NULL;
	name = init_str();
	nl = 0;
	add_to_str(&name, &nl, nm);
	free(nm);
	get_filename_from_url(url, &fn, &l);
	fnnn = NULL;
	for (fnn = fn; fnn < fn + l; fnn++) if (*fnn == '.') fnnn = fnn;
	if (fnnn && (s = memacpy(fnnn, l - (fnnn - fn)))) {
		check_shell_security(&s);
		add_to_str(&name, &nl, s);
		mem_free(s);
	}
	return name;
}

unsigned char *subst_file(unsigned char *prog, unsigned char *file)
{
	unsigned char *n = init_str();
	int l = 0;
	while (*prog) {
		int p;
		for (p = 0; prog[p] && prog[p] != '%'; p++) ;
		add_bytes_to_str(&n, &l, prog, p);
		prog += p;
		if (*prog == '%') {
			add_to_str(&n, &l, file);
			prog++;
		}
	}
	return n;
}

void start_download(struct session *ses, unsigned char *file)
{
	struct download *down;
	int h;
	unsigned char *url = ses->dn_url;
	if (!url) return;
	kill_downloads_to_file(file);
	if ((h = create_download_file(ses->term, file, 0)) == -1) return;
	if (!(down = mem_alloc(sizeof(struct download)))) return;
	memset(down, 0, sizeof(struct download));
	down->url = stracpy(url);
	down->stat.end = (void (*)(struct status *, void *))download_data;
	down->stat.data = down;
	down->last_pos = 0;
	down->file = stracpy(file);
	down->handle = h;
	down->ses = ses;
	down->remotetime = 0;
	add_to_list(downloads, down);
	load_url(url, &down->stat, PRI_DOWNLOAD, NC_CACHE);
	display_download(ses->term, down, ses);
}

void tp_cancel(struct session *);

void tp_free(struct session *);

void continue_download(struct session *ses, unsigned char *file)
{
	struct download *down;
	int h;
	unsigned char *url = ses->tq_url;
	if (!url) return;
	if (ses->tq_prog) {
		if (!(file = get_temp_name(url))) {
			tp_cancel(ses);
			return;
		}
	}
	kill_downloads_to_file(file);
	if ((h = create_download_file(ses->term, file, !!ses->tq_prog)) == -1) {
		tp_cancel(ses);
		if (ses->tq_prog) mem_free(file);
		return;
	}
	if (!(down = mem_alloc(sizeof(struct download)))) {
		tp_cancel(ses);
		if (ses->tq_prog) mem_free(file);
		return;
	}
	memset(down, 0, sizeof(struct download));
	down->url = stracpy(url);
	down->stat.end = (void (*)(struct status *, void *))download_data;
	down->stat.data = down;
	down->last_pos = 0;
	down->file = stracpy(file);
	down->handle = h;
	down->ses = ses;
	if (ses->tq_prog) {
		down->prog = subst_file(ses->tq_prog, file);
		mem_free(file);
		mem_free(ses->tq_prog);
		ses->tq_prog = NULL;
	}
	down->prog_flags = ses->tq_prog_flags;
	add_to_list(downloads, down);
	change_connection(&ses->tq, &down->stat, PRI_DOWNLOAD);
	tp_free(ses);
	display_download(ses->term, down, ses);
}

void tp_free(struct session *ses)
{
	ses->tq_ce->refcount--;
	mem_free(ses->tq_url);
	ses->tq_url = NULL;
	if (ses->tq_goto_position) mem_free(ses->tq_goto_position), ses->tq_goto_position = NULL;
	ses->tq_ce = NULL;
}

void tp_cancel(struct session *ses)
{
	change_connection(&ses->tq, NULL, PRI_CANCEL);
	tp_free(ses);
}

void tp_save(struct session *ses)
{
	if (ses->tq_prog) mem_free(ses->tq_prog), ses->tq_prog = NULL;
	query_file(ses, ses->tq_url, continue_download, tp_cancel);
}

void tp_open(struct session *ses)
{
	continue_download(ses, "");
}

void display_timer(struct session *);

void tp_display(struct session *ses)	/* !!! FIXME: frames */
{
	struct location *l;
	if (!(l = mem_alloc(sizeof(struct location) + strlen(ses->tq_url)))) return;
	memset(l, 0, sizeof(struct location));
	init_list(l->frames);
	memcpy(&l->stat, &ses->tq, sizeof(struct status));
	init_vs(&l->vs, ses->tq_url);
	if (ses->tq_goto_position) {
		l->vs.goto_position = ses->tq_goto_position;
		ses->tq_goto_position = NULL;
	}
	add_to_list(ses->history, l);
	cur_loc(ses)->stat.end = (void (*)(struct status *, void *))doc_end_load;
	cur_loc(ses)->stat.data = ses;
	if (ses->tq.state >= 0) change_connection(&ses->tq, &cur_loc(ses)->stat, PRI_MAIN);
	else cur_loc(ses)->stat.state = ses->tq.state;
	cur_loc(ses)->vs.plain = 1;
	display_timer(ses);
	tp_free(ses);
}

void type_query(struct session *ses, struct cache_entry *ce, unsigned char *ct, struct assoc *a)
{
	unsigned char *m1;
	unsigned char *m2;
	if (ses->tq_prog) mem_free(ses->tq_prog), ses->tq_prog = NULL;
	if (a) ses->tq_prog = stracpy(a->prog), ses->tq_prog_flags = a->block;
	if (a && !a->ask) {
		tp_open(ses);
		return;
	}
	m1 = stracpy(ct);
	if (!a) {
		if (!anonymous) msg_box(ses->term, getml(m1, NULL), TEXT(T_UNKNOWN_TYPE), AL_CENTER | AL_EXTD_TEXT, TEXT(T_CONTEN_TYPE_IS), " ", m1, ".\n", TEXT(T_DO_YOU_WANT_TO_SAVE_OR_DISLPAY_THIS_FILE), NULL, ses, 3, TEXT(T_SAVE), tp_save, B_ENTER, TEXT(T_DISPLAY), tp_display, 0, TEXT(T_CANCEL), tp_cancel, B_ESC);
		else msg_box(ses->term, getml(m1, NULL), TEXT(T_UNKNOWN_TYPE), AL_CENTER | AL_EXTD_TEXT, TEXT(T_CONTEN_TYPE_IS), " ", m1, ".\n", TEXT(T_DO_YOU_WANT_TO_SAVE_OR_DISLPAY_THIS_FILE), NULL, ses, 2, TEXT(T_DISPLAY), tp_display, B_ENTER, TEXT(T_CANCEL), tp_cancel, B_ESC);
	} else {
		m2 = stracpy(a->label ? a->label : (unsigned char *)"");
		if (!anonymous) msg_box(ses->term, getml(m1, m2, NULL), TEXT(T_WHAT_TO_DO), AL_CENTER | AL_EXTD_TEXT, TEXT(T_CONTEN_TYPE_IS), " ", m1, ".\n", TEXT(T_DO_YOU_WANT_TO_OPEN_FILE_WITH), " ", m2, ", ", TEXT(T_SAVE_IT_OR_DISPLAY_IT), NULL, ses, 4, TEXT(T_OPEN), tp_open, B_ENTER, TEXT(T_SAVE), tp_save, 0, TEXT(T_DISPLAY), tp_display, 0, TEXT(T_CANCEL), tp_cancel, B_ESC);
		else msg_box(ses->term, getml(m1, m2, NULL), TEXT(T_WHAT_TO_DO), AL_CENTER | AL_EXTD_TEXT, TEXT(T_CONTEN_TYPE_IS), " ", m1, ".\n", TEXT(T_DO_YOU_WANT_TO_OPEN_FILE_WITH), " ", m2, ", ", TEXT(T_SAVE_IT_OR_DISPLAY_IT), NULL, ses, 3, TEXT(T_OPEN), tp_open, B_ENTER, TEXT(T_DISPLAY), tp_display, 0, TEXT(T_CANCEL), tp_cancel, B_ESC);
	}
}

int ses_chktype(struct session *ses, struct status **stat, struct cache_entry *ce)
{
	struct assoc *a;
	unsigned char *ct;
	int r = 0;
	if (!(ct = get_content_type(ce->head, ce->url))) goto f;
	if (!strcasecmp(ct, "text/html")) goto ff;
	r = 1;
	if (!strcasecmp(ct, "text/plain")) goto ff;
	if (!(a = get_type_assoc(ses->term, ct)) && strlen(ct) >= 4 && !casecmp(ct, "text", 4)) goto ff;
	if (ses->tq_url) internal("type query to %s already in prgress", ses->tq_url);
	ses->tq_url = stracpy(ses->loading_url);
	change_connection(&ses->loading, *stat = &ses->tq, PRI_MAIN);
	(ses->tq_ce = ce)->refcount++;
	if (ses->tq_goto_position) mem_free(ses->tq_goto_position);
	ses->tq_goto_position = stracpy(ses->goto_position);
	type_query(ses, ce, ct, a);
	mem_free(ct);
	return 1;

	ff:
	mem_free(ct);
	f:
	if (ses->wtd_target && r) *ses->wtd_target = 0;
	ses_forward(ses);
	cur_loc(ses)->vs.plain = r;
	return 0;
}

struct wtd_data {
	struct session *ses;
	unsigned char *url;
	int pri;
	int cache;
	int wtd;
	unsigned char *target;
	unsigned char *pos;
	void (*fn)(struct status *, struct session *);
};

void post_yes(struct wtd_data *w)
{
	abort_preloading(w->ses);
	if (w->ses->goto_position) mem_free(w->ses->goto_position);
	w->ses->goto_position = stracpy(w->pos);
	w->ses->loading.end = (void (*)(struct status *, void *))w->fn;
	w->ses->loading.data = w->ses;
	w->ses->loading_url = stracpy(w->url);
	w->ses->wtd = w->wtd;
	w->ses->wtd_target = w->target;
	load_url(w->ses->loading_url, &w->ses->loading, w->pri, w->cache);
}

void post_no(struct wtd_data *w)
{
	*strchr(w->url, POST_CHAR) = 0;
	post_yes(w);
}

void post_cancel(struct wtd_data *w)
{
	reload(w->ses, NC_CACHE);
}

void ses_goto(struct session *ses, unsigned char *url, unsigned char *target, int pri, int cache, int wtd, unsigned char *pos, void (*fn)(struct status *, struct session *), int redir)
{
	struct wtd_data *w;
	unsigned char *m1, *m2;
	struct cache_entry *e;
	if (!strchr(url, POST_CHAR) || (cache == NC_ALWAYS_CACHE && !find_in_cache(url, &e) && !e->incomplete) || !(w = mem_alloc(sizeof(struct wtd_data)))) {
		if (ses->goto_position) mem_free(ses->goto_position);
		ses->goto_position = pos;
		ses->loading.end = (void (*)(struct status *, void *))fn;
		ses->loading.data = ses;
		ses->loading_url = url;
		ses->wtd = wtd;
		ses->wtd_target = target;
		load_url(url, &ses->loading, pri, cache);
		return;
	}
	w->ses = ses;
	w->url = url;
	w->pri = pri;
	w->cache = cache;
	w->wtd = wtd;
	w->target = target;
	w->pos = pos;
	w->fn = fn;
	if (redir) m1 = TEXT(T_DO_YOU_WANT_TO_FOLLOW_REDIRECT_AND_POST_FORM_DATA_TO_URL);
	else if (wtd == WTD_FORWARD) m1 = TEXT(T_DO_YOU_WANT_TO_POST_FORM_DATA_TO_URL);
	else m1 = TEXT(T_DO_YOU_WANT_TO_REPOST_FORM_DATA_TO_URL);
	m2 = memacpy(url, (unsigned char *)strchr(url, POST_CHAR) - url);
	msg_box(ses->term, getml(m2, w, w->url, w->pos, NULL), TEXT(T_WARNING), AL_CENTER | AL_EXTD_TEXT, m1, " ", m2, "?", NULL, w, 3, TEXT(T_YES), post_yes, B_ENTER, TEXT(T_NO), post_no, 0, TEXT(T_CANCEL), post_cancel, B_ESC);
}

int do_move(struct session *ses, struct status **stat)
{
	struct cache_entry *ce = NULL;
	if (!ses->loading_url) {
		internal("no ses->loading_url");
		return 0;
	}
	if (!(ce = (*stat)->ce) || (ses->wtd == WTD_IMGMAP && (*stat)->state >= 0)) {
		return 0;
	}
	if (ce->redirect && ses->redirect_cnt++ < MAX_REDIRECTS) {
		unsigned char *u, *p, *gp;
		int w = ses->wtd;
		if (ses->wtd == WTD_BACK && (void *)cur_loc(ses)->next == &ses->history)
			goto b;
		if (!(u = join_urls(ses->loading_url, ce->redirect))) goto b;
		if (!http_bugs.bug_302_redirect) if (!ce->redirect_get && (p = strchr(ses->loading_url, POST_CHAR))) add_to_strn(&u, p);
		/* ^^^^ According to RFC2068 POST must not be redirected to GET, but
			some BUGGY message boards rely on it :-( */
		gp = stracpy(ses->goto_position);
		abort_loading(ses);
		if (!list_empty(ses->history)) *stat = &cur_loc(ses)->stat;
		else *stat = NULL;
		if (w == WTD_FORWARD || w == WTD_IMGMAP) {
			ses_goto(ses, u, ses->wtd_target, PRI_MAIN, NC_CACHE, w, gp, end_load, 1);
			return 2;
		}
		if (gp) mem_free(gp);
		if (w == WTD_BACK) {
			ses_goto(ses, u, NULL, PRI_MAIN, NC_CACHE, WTD_RELOAD, NULL, end_load, 1);
			return 2;
		}
		if (w == WTD_RELOAD) {
			ses_goto(ses, u, NULL, PRI_MAIN, ses->reloadlevel, WTD_RELOAD, NULL, end_load, 1);
			return 2;
		}
	} else ses->redirect_cnt = 0;
	b:
	if (ses->display_timer != -1) kill_timer(ses->display_timer), ses->display_timer = -1;
	if (ses->wtd == WTD_FORWARD) {
		if (ses_chktype(ses, stat, ce)) {
			free_wtd(ses);
			reload(ses, NC_CACHE);
			return 2;
		}
	}
	if (ses->wtd == WTD_IMGMAP) ses_imgmap(ses);
	if (ses->wtd == WTD_BACK) ses_back(ses);
	if (ses->wtd == WTD_RELOAD) ses_back(ses), ses_forward(ses);
	if ((*stat)->state >= 0) change_connection(&ses->loading, *stat = &cur_loc(ses)->stat, PRI_MAIN);
	else cur_loc(ses)->stat.state = ses->loading.state;
	free_wtd(ses);
	return 1;
}

void request_frameset(struct session *, struct frameset_desc *);

void request_frame(struct session *ses, unsigned char *name, unsigned char *uurl)
{
	struct location *loc = cur_loc(ses);
	struct frame *frm;
	unsigned char *url, *pos;
	if (list_empty(ses->history)) {
		internal("request_frame: no location");
		return;
	}
	foreach(frm, loc->frames) if (!strcasecmp(frm->name, name)) {
		url = stracpy(frm->vs.url);
		if (frm->vs.f && frm->vs.f->f_data && frm->vs.f->f_data->frame) {
			/*del_from_list(frm);*/
			request_frameset(ses, frm->vs.f->f_data->frame_desc);
			/*destroy_vs(&frm->vs);
			mem_free(frm->name);
			mem_free(frm);*/
			mem_free(url);
			return;
		}
		goto found;
	}
	url = stracpy(uurl);
	pos = extract_position(url);
	if (!(frm = mem_alloc(sizeof(struct frame) + strlen(url) + 1))) {
		mem_free(url); if (pos) mem_free(pos);
		return;
	}
	memset(frm, 0, sizeof(struct frame));
	if (!(frm->name = stracpy(name))) {
		mem_free(frm);
		return;
	}
	init_vs(&frm->vs, url);
	if (pos) frm->vs.goto_position = pos;
	add_to_list(loc->frames, frm);
	found:
	if (*url) request_additional_file(ses, url, PRI_FRAME);
	mem_free(url);
}

void request_frameset(struct session *ses, struct frameset_desc *fd)
{
	int i;
	static int depth = 0;
	if (++depth <= HTML_MAX_FRAME_DEPTH) {
		for (i = 0; i < fd->n; i++) {
			if (fd->f[i].subframe) request_frameset(ses, fd->f[i].subframe);
			else if (fd->f[i].name) request_frame(ses, fd->f[i].name, fd->f[i].url);
		}
	}
	depth--;
}

void load_frames(struct session *ses, struct f_data_c *fd)
{
	struct f_data *ff = fd->f_data;
	if (!ff || !ff->frame) return;
	request_frameset(ses, ff->frame_desc);
}

void display_timer(struct session *ses)
{
	ttime t = get_time();
	html_interpret(ses);
	draw_formatted(ses);
	t = (get_time() - t) * DISPLAY_TIME;
	if (t < DISPLAY_TIME_MIN) t = DISPLAY_TIME_MIN;
	ses->display_timer = install_timer(t, (void (*)(void *))display_timer, ses);
	load_frames(ses, ses->screen);
	process_file_requests(ses);
}
	
struct list_head questions_queue = {&questions_queue, &questions_queue};

struct questions_entry {
	struct questions_entry *next;
	struct questions_entry *prev;
	void (*callback)(struct session *);
};

void check_questions_queue(struct session *ses)
{
	struct questions_entry *q;
	while (!list_empty(questions_queue)) {
		q = questions_queue.next;
		q->callback(ses);
		del_from_list(q);
		mem_free(q);
	}
}

void add_questions_entry(void *callback)
{
	struct questions_entry *q;
	if (!(q = mem_alloc(sizeof(struct questions_entry)))) return;
	q->callback = callback;
	add_to_list(questions_queue, q);
}

void end_load(struct status *stat, struct session *ses)
{
	int d;
	if (!ses->wtd) {
		internal("end_load: !ses->wtd");
		return;
	}
	d = do_move(ses, &stat);
	if (!stat) return;
	if (d == 1) {
		stat->end = (void (*)(struct status *, void *))doc_end_load;
		display_timer(ses);
	}
	if (stat->state < 0) {
		if (d != 2 && ses->wtd) {
			free_wtd(ses);
		}
		if (d == 1) doc_end_load(stat, ses);
	}
	if (stat->state < 0 && stat->state != S_OK && d != 2) {
		print_error_dialog(ses, stat, TEXT(T_ERROR));
	}
	if (stat->state == S_QUESTIONS) check_questions_queue(ses);
	print_screen_status(ses);
}

void doc_end_load(struct status *stat, struct session *ses)
{
	if (stat->state < 0) {
		if (ses->display_timer != -1) kill_timer(ses->display_timer), ses->display_timer = -1;
		html_interpret(ses);
		draw_formatted(ses);
		load_frames(ses, ses->screen);
		process_file_requests(ses);
		if (stat->state != S_OK) print_error_dialog(ses, stat, TEXT(T_ERROR));
	} else if (ses->display_timer == -1) display_timer(ses);
	if (stat->state == S_QUESTIONS) check_questions_queue(ses);
	print_screen_status(ses);
}

void file_end_load(struct status *stat, struct file_to_load *ftl)
{
	if (ftl->stat.ce) {
		if (ftl->ce) ftl->ce->refcount--;
		(ftl->ce = ftl->stat.ce)->refcount++;
	}
	doc_end_load(stat, ftl->ses);
}

struct file_to_load *request_additional_file(struct session *ses, unsigned char *url, int pri)
{
	struct file_to_load *ftl;
	foreach(ftl, ses->more_files) {
		if (!strcmp(ftl->url, url)) {
			if (ftl->pri > pri) {
				ftl->pri = pri;
				change_connection(&ftl->stat, &ftl->stat, pri);
			}
			return NULL;
		}
	}
	if (!(ftl = mem_alloc(sizeof(struct file_to_load)))) return NULL;
	if (!(ftl->url = stracpy(url))) {
		mem_free(ftl);
		return NULL;
	}
	ftl->stat.end = (void (*)(struct status *, void *))file_end_load;
	ftl->stat.data = ftl;
	ftl->req_sent = 0;
	ftl->pri = pri;
	ftl->ce = NULL;
	ftl->ses = ses;
	add_to_list(ses->more_files, ftl);
	return ftl;
}

struct file_to_load *request_additional_loading_file(struct session *ses, unsigned char *url, struct status *stat, int pri)
{
	struct file_to_load *ftl;
	if (!(ftl = request_additional_file(ses, url, pri))) {
		change_connection(stat, NULL, PRI_CANCEL);
		return NULL;
	}
	ftl->req_sent = 1;
	ftl->ce = stat->ce;
	change_connection(stat, &ftl->stat, pri);
	return ftl;
}

void process_file_requests(struct session *ses)
{
	static int stop_recursion = 0;
	struct file_to_load *ftl;
	int more;
	if (stop_recursion) return;
	stop_recursion = 1;
	do {
		more = 0;
		foreach(ftl, ses->more_files) if (!ftl->req_sent) {
			ftl->req_sent = 1;
			load_url(ftl->url, &ftl->stat, ftl->pri, NC_CACHE);
			more = 1;
		}
	} while (more);
	stop_recursion = 0;
}

struct session *create_session(struct window *win)
{
	struct terminal *term = win->term;
	struct session *ses;
	if ((ses = mem_alloc(sizeof(struct session)))) {
		memset(ses, 0, sizeof(struct session));
		init_list(ses->history);
		init_list(ses->scrn_frames);
		init_list(ses->more_files);
		ses->term = term;
		ses->win = win;
		ses->id = session_id++;
		ses->screen = NULL;
		ses->wtd = WTD_NO;
		ses->display_timer = -1;
		ses->loading_url = NULL;
		ses->goto_position = NULL;
		memcpy(&ses->ds, &dds, sizeof(struct document_setup));
		add_to_list(sessions, ses);
	}
	if (first_use) {
		first_use = 0;
		msg_box(term, NULL, TEXT(T_WELCOME), AL_CENTER | AL_EXTD_TEXT, TEXT(T_WELCOME_TO_LINKS), "\n\n", TEXT(T_BASIC_HELP), NULL, NULL, 1, TEXT(T_OK), NULL, B_ENTER | B_ESC);
	}
	return ses;
}

void copy_session(struct session *old, struct session *new)
{
	/*struct location *l;
	foreachback(l, old->history) {
		struct location *nl;
		if ((nl = mem_alloc(sizeof(struct location) + strlen(l->vs.url) + 1))) {
			struct frame *frm;
			memcpy(nl, l, sizeof(struct location) + strlen(l->vs.url) + 1);
			init_list(nl->frames);
			foreachback(frm, l->frames) {
				struct frame *nfrm;
				if ((nfrm = mem_alloc(sizeof(struct frame) + strlen(frm->vs.url) + 1))) {
					memcpy(nfrm, frm, sizeof(struct frame) + strlen(frm->vs.url) + 1);
					add_to_list(nl->frames, nfrm);
				}
			}
			add_to_list(new->history, nl);
		}
	}*/
	if (!list_empty(old->history)) {
		goto_url(new, cur_loc(old)->vs.url);
	}
}

void *create_session_info(int cp, unsigned char *url, int *ll)
{
	int l = strlen(url);
	int *i;
	*ll = 2 * sizeof(int) + l;
	if (!(i = mem_alloc(2 * sizeof(int) + l))) return NULL;
	i[0] = cp;
	i[1] = l;
	memcpy(i + 2, url, l);
	return i;
}

static inline unsigned char hx(int a)
{
	return a >= 10 ? a + 'A' - 10 : a + '0';
}

static inline int unhx(unsigned char a)
{
	if (a >= '0' && a <= '9') return a - '0';
	if (a >= 'A' && a <= 'F') return a - 'A' + 10;
	if (a >= 'a' && a <= 'f') return a - 'a' + 10;
	return -1;
}

unsigned char *encode_url(unsigned char *url)
{
	unsigned char *u = init_str();
	int l = 0;
	for (; *url; url++) {
		if (is_safe_in_shell(*url) && *url != '+') add_chr_to_str(&u, &l, *url);
		else add_chr_to_str(&u, &l, '+'), add_chr_to_str(&u, &l, hx(*url >> 4)), add_chr_to_str(&u, &l, hx(*url & 0xf));
	}
	return u;
}

unsigned char *decode_url(unsigned char *url)
{
	unsigned char *u = init_str();
	int l = 0;
	for (; *url; url++) {
		if (*url != '+' || unhx(url[1]) == -1 || unhx(url[2]) == -1) add_chr_to_str(&u, &l, *url);
		else add_chr_to_str(&u, &l, (unhx(url[1]) << 4) + unhx(url[2])), url += 2;
	}
	return u;
}

int read_session_info(int fd, struct session *ses, void *data, int len)
{
	unsigned char *h;
	int cpfrom, sz;
	struct session *s;
	if (len < 2 * sizeof(int)) return -1;
	cpfrom = *(int *)data;
	sz = *((int *)data + 1);
	foreach(s, sessions) if (s->id == cpfrom) {
		copy_session(s, ses);
		break;
	}
	if (sz) {
		char *u, *uu;
		if (len < 2 * sizeof(int) + sz) return 0;
		if ((u = mem_alloc(sz + 1))) {
			memcpy(u, (int *)data + 2, sz);
			u[sz] = 0;
			uu = decode_url(u);
			goto_url(ses, uu);
			mem_free(u);
			mem_free(uu);
		}
	} else if ((h = getenv("WWW_HOME")) && *h) goto_url(ses, h);
	return 0;
}

void abort_preloading(struct session *ses)
{
	if (ses->wtd) {
		change_connection(&ses->loading, NULL, PRI_CANCEL);
		free_wtd(ses);
	}
}

void abort_loading(struct session *ses)
{
	struct location *l = cur_loc(ses);
	if ((void *)l != &ses->history) {
		if (l->stat.state >= 0)
			change_connection(&l->stat, NULL, PRI_CANCEL);
		abort_files_load(ses);
	}
	abort_preloading(ses);
}

void destroy_session(struct session *ses)
{
	struct f_data_c *fdc;
	struct download *d;
	struct location *l;
	if (!ses) return;
	foreach(d, downloads) if (d->ses == ses && d->prog) {
		d = d->prev;
		abort_download(d->next);
	}
	abort_loading(ses);
	free_files(ses);
	if (ses->screen) detach_formatted(ses->screen), mem_free(ses->screen);
	foreach(fdc, ses->scrn_frames) detach_formatted(fdc);
	free_list(ses->scrn_frames);
	while ((void *)(l = ses->history.next) != &ses->history) {
		struct frame *frm;
		while ((void *)(frm = l->frames.next) != &l->frames) {
			destroy_vs(&frm->vs);
			mem_free(frm->name);
			del_from_list(frm);
			mem_free(frm);
		}
		destroy_vs(&l->vs);
		del_from_list(l);
		mem_free(l);
	}
	if (ses->loading_url) mem_free(ses->loading_url);
	if (ses->display_timer != -1) kill_timer(ses->display_timer);
	if (ses->goto_position) mem_free(ses->goto_position);
	if (ses->imgmap_href_base) mem_free(ses->imgmap_href_base);
	if (ses->imgmap_target_base) mem_free(ses->imgmap_target_base);
	if (ses->tq_ce) ses->tq_ce->refcount--;
	if (ses->tq_url) {
		change_connection(&ses->tq, NULL, PRI_CANCEL);
		mem_free(ses->tq_url);
	}
	if (ses->tq_goto_position) mem_free(ses->tq_goto_position);
	if (ses->tq_prog) mem_free(ses->tq_prog);
	if (ses->dn_url) mem_free(ses->dn_url);
	if (ses->search_word) mem_free(ses->search_word);
	if (ses->last_search_word) mem_free(ses->last_search_word);
	del_from_list(ses);
	/*mem_free(ses);*/
}

void destroy_all_sessions()
{
	/*while (!list_empty(sessions)) destroy_session(sessions.next);*/
}

void abort_all_downloads()
{
	while (!list_empty(downloads)) abort_download(downloads.next);
}

void reload(struct session *ses, int no_cache)
{
	struct location *l;
	abort_loading(ses);
	if (no_cache == -1) no_cache = ++ses->reloadlevel;
	else ses->reloadlevel = no_cache;
	if ((void *)(l = ses->history.next) != &ses->history) {
		struct file_to_load *ftl;
		l->stat.data = ses;
		l->stat.end = (void *)doc_end_load;
		load_url(l->vs.url, &l->stat, PRI_MAIN, no_cache);
		foreach(ftl, ses->more_files) {
			if (ftl->req_sent && ftl->stat.state >= 0) continue;
			ftl->stat.data = ftl;
			ftl->stat.end = (void *)file_end_load;
			load_url(ftl->url, &ftl->stat, PRI_FRAME, no_cache);
		}
	}
}

/*void ses_load_notify(struct status *stat, struct session *ses)
{
	if (stat->state == S_TRANS || stat->state == S_OK) {
		stat->end = (void (*)(struct status *, void *))end_load;
		ses->wtd = WTD_NO;
		mem_free(ses->loading_url);
		if (ses->wtd == WTD_FORWARD) {
			ses_forward(ses);
		} else internal("bad ses->wtd");
		return;
	}
	if (stat->state >= 0) print_screen_status(ses);
	if (stat->state < 0) print_error_dlg(ses, stat);
}*/

void go_back(struct session *ses)
{
	unsigned char *url;
	ses->reloadlevel = NC_CACHE;
	if (ses->wtd) {
		if (1 || ses->wtd != WTD_BACK) {
			abort_loading(ses);
			print_screen_status(ses);
			reload(ses, NC_CACHE);
		}
		return;
	}
	if (ses->history.next == &ses->history || ses->history.next == ses->history.prev)
		return;
	abort_loading(ses);
	if (!(url = stracpy(((struct location *)ses->history.next)->next->vs.url)))
		return;
	ses_goto(ses, url, NULL, PRI_MAIN, NC_ALWAYS_CACHE, WTD_BACK, NULL, end_load, 0);
}

void goto_url_w(struct session *ses, unsigned char *url, unsigned char *target, int wtd)
{
	unsigned char *u;
	unsigned char *pos;
	void (*fn)(struct session *, unsigned char *);
	if ((fn = get_external_protocol_function(url))) {
		fn(ses, url);
		return;
	}
	ses->reloadlevel = NC_CACHE;
	/*struct location *l = ses->history.next;*/
	if (!(u = translate_url(url, ses->term->cwd))) {
		struct status stat = { NULL, NULL, NULL, NULL, S_BAD_URL, PRI_CANCEL, 0, NULL, NULL };
		print_error_dialog(ses, &stat, TEXT(T_ERROR));
		return;
	}
	pos = extract_position(u);
	if (ses->wtd == wtd) {
		if (!strcmp(ses->loading_url, u)) {
			mem_free(u);
			if (ses->goto_position) mem_free(ses->goto_position);
			ses->goto_position = pos;
			return;
		}
	}
	abort_loading(ses);
	ses_goto(ses, u, target, PRI_MAIN, NC_CACHE, wtd, pos, end_load, 0);
	/*abort_loading(ses);*/
}

void goto_url_f(struct session *ses, unsigned char *url, unsigned char *target)
{
	goto_url_w(ses, url, target, WTD_FORWARD);
}

void goto_url(struct session *ses, unsigned char *url)
{
	goto_url_w(ses, url, NULL, WTD_FORWARD);
}

void goto_imgmap(struct session *ses, unsigned char *url, unsigned char *href, unsigned char *target)
{
	if (ses->imgmap_href_base) mem_free(ses->imgmap_href_base);
	ses->imgmap_href_base = href;
	if (ses->imgmap_target_base) mem_free(ses->imgmap_target_base);
	ses->imgmap_target_base = target;
	goto_url_w(ses, url, target, WTD_IMGMAP);
}

struct frame *ses_find_frame(struct session *ses, unsigned char *name)
{
	struct location *l = cur_loc(ses);
	struct frame *frm;
	if (list_empty(ses->history)) {
		internal("ses_request_frame: history empty");
		return NULL;
	}
	foreachback(frm, l->frames) if (!strcasecmp(frm->name, name)) return frm;
	/*internal("ses_find_frame: frame not found");*/
	return NULL;
}

struct frame *ses_change_frame_url(struct session *ses, unsigned char *name, unsigned char *url)
{
	struct location *l = cur_loc(ses);
	struct frame *frm;
	if (list_empty(ses->history)) {
		internal("ses_change_frame_url: history empty");
		return NULL;
	}
	foreachback(frm, l->frames) if (!strcasecmp(frm->name, name)) {
		if (strlen(url) > strlen(frm->vs.url)) {
			struct f_data_c *fd;
			struct frame *nf = frm;
			if (!(nf = mem_realloc(frm, sizeof(struct frame) + strlen(url) + 1))) return NULL;
			nf->prev->next = nf->next->prev = nf;
			foreach(fd, ses->scrn_frames) {
				if (fd->vs == &frm->vs) fd->vs = &nf->vs;
			}
			frm = nf;
		}
		strcpy(frm->vs.url, url);
		return frm;
	}
	return NULL;
	
}

void win_func(struct window *win, struct event *ev, int fw)
{
	struct session *ses = win->data;
	switch (ev->ev) {
		case EV_ABORT:
			destroy_session(ses);
			break;
		case EV_INIT:
			if (!(ses = win->data = create_session(win)) ||
			    read_session_info(win->term->fdin, ses, (char *)ev->b + sizeof(int), *(int *)ev->b)) {
				destroy_terminal(win->term);
				return;
			}
			/*make_request(ses, 0);*/
		case EV_RESIZE:
			html_interpret(ses);
			draw_formatted(ses);
			load_frames(ses, ses->screen);
			process_file_requests(ses);
			print_screen_status(ses);
			break;
		case EV_REDRAW:
			draw_formatted(ses);
			print_screen_status(ses);
			break;
		case EV_KBD:
		case EV_MOUSE:
			send_event(ses, ev);
			break;
		default:
			error("ERROR: unknown event");
	}
}

/* 
  Gets the url being viewed by this session. Writes it into str.
  A maximum of str_size bytes (including null) will be written.
*/  
unsigned char *get_current_url(struct session *ses, unsigned char *str, size_t str_size) {
	unsigned char *here, *end_of_url;
	int l = 0;
	size_t url_len = 0;

	/* Not looking at anything */
	if (list_empty(ses->history))
		return NULL;

	here = cur_loc(ses)->vs.url;

	/* Find the length of the url */
    if (end_of_url = strchr(here, POST_CHAR)) {
		url_len = (size_t)(end_of_url - (unsigned char *)here);
	} else {
		url_len = strlen(here);
	}

	/* Ensure that the url size is not greater than str_size */ 
	if (url_len > str_size)
			url_len = str_size - 1;

	strncpy(str, here, url_len);

	/* Ensure null termination */
	str[url_len] = '\0';
	
	return str;
}


/* 
  Gets the title of the page being viewed by this session. Writes it into str.
  A maximum of str_size bytes (including null) will be written.
*/  
unsigned char *get_current_title(struct session *ses, unsigned char *str, size_t str_size) {
	struct f_data_c *fd;
	fd = (struct f_data_c *)current_frame(ses);

	/* Ensure that the title is defined */
	if (!fd)
		return NULL;

	return safe_strncpy(str, fd->f_data->title, str_size);
}

/* 
  Gets the url of the link currently selected. Writes it into str.
  A maximum of str_size bytes (including null) will be written.
*/  
unsigned char *get_current_link_url(struct session *ses, unsigned char *str, size_t str_size) {
	struct f_data_c *fd;
    struct link *l;
	
	fd = (struct f_data_c *)current_frame(ses);
	/* What the hell is an 'fd'? */
	if (!fd)
		return NULL;
	
	/* Nothing selected? */
    if (fd->vs->current_link == -1) 
		return NULL;

    l = &fd->f_data->links[fd->vs->current_link];
	/* Only write a link */
    if (l->type != L_LINK) 
		return NULL;
	
	return safe_strncpy(str, l->where, str_size);
}
