1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-20 00:15:31 +00:00
elinks/src/session/task.c
Witold Filipczyk 54f1426e43 [status] Introduced print_screen_status_delayed.
For cases when status is updated often. For example
uploads or downloads.
2023-01-18 21:35:50 +01:00

804 lines
20 KiB
C

/** Sessions task management
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "bfu/menu.h"
#include "bfu/dialog.h"
#include "cache/cache.h"
#include "dialogs/menu.h"
#include "dialogs/status.h"
#include "document/document.h"
#include "document/html/parser.h"
#include "document/refresh.h"
#include "document/view.h"
#include "intl/libintl.h"
#include "main/event.h"
#include "main/timer.h"
#include "network/connection.h"
#include "osdep/newwin.h"
#include "protocol/protocol.h"
#include "protocol/uri.h"
#include "terminal/terminal.h"
#include "terminal/window.h"
#include "session/download.h"
#include "session/location.h"
#include "session/session.h"
#include "session/task.h"
#include "viewer/text/view.h"
static void loading_callback(struct download *, struct session *);
static void
free_task(struct session *ses)
{
assertm(ses->task.type, "Session has no task");
if_assert_failed return;
if (ses->loading_uri) {
done_uri(ses->loading_uri);
ses->loading_uri = NULL;
}
ses->task.type = TASK_NONE;
mem_free_set(&ses->task.target.frame, NULL);
}
void
abort_preloading(struct session *ses, int interrupt)
{
if (!ses->task.type) {
/* ses->task.target.frame must be freed in the
* destroy_session() => abort_loading() =>
* abort_preloading() call chain. Normally,
* free_task() called from here would do that,
* but if the task has no type, free_task()
* cannot be called because an assertion would
* fail. There are several functions that set
* ses->task.target.frame without ses->task.type,
* so apparently it is allowed. */
mem_free_set(&ses->task.target.frame, NULL);
return;
}
cancel_download(&ses->loading, interrupt);
free_task(ses);
}
struct task {
struct session *ses;
struct uri *uri;
cache_mode_T cache_mode;
struct session_task session_task;
};
void
ses_load(struct session *ses, struct uri *uri, char *target_frame,
struct location *target_location, cache_mode_T cache_mode,
enum task_type task_type)
{
ses->loading.callback = (download_callback_T *) loading_callback;
ses->loading.data = ses;
ses->loading_uri = uri;
ses->task.type = task_type;
mem_free_set(&ses->task.target.frame, null_or_stracpy(target_frame));
ses->task.target.location = target_location;
load_uri(ses->loading_uri, ses->referrer, &ses->loading,
PRI_MAIN, cache_mode, -1);
}
static void
post_yes(void *task_)
{
struct task *task = (struct task *)task_;
abort_preloading(task->ses, 0);
/* XXX: Make the session inherit the URI. */
ses_load(task->ses, task->uri, task->session_task.target.frame,
task->session_task.target.location, task->cache_mode,
task->session_task.type);
}
static void
post_no(void *task_)
{
struct task *task = (struct task *)task_;
reload(task->ses, CACHE_MODE_NORMAL);
done_uri(task->uri);
}
/** Check if the URI is obfuscated (bug 382). The problem is said to occur when
* a URI designed to pass access a specific location with a supplied username,
* contains misleading chars prior to the @ symbol.
*
* An attacker can exploit this issue by supplying a malicious URI pointing to
* a page designed to mimic that of a trusted site, and tricking a victim who
* follows a link into believing they are actually at the trusted location.
*
* Only the user ID (and not also the password) is checked because only the
* user ID is displayed in the status bar. */
static int
check_malicious_uri(struct uri *uri)
{
char *user, *pos;
int warn = 0;
assert(uri->user && uri->userlen);
user = pos = memacpy(uri->user, uri->userlen);
if (!user) return 0;
decode_uri_for_display(user);
while (*pos) {
int length, trailing_dots;
for (length = 0; pos[length] != '\0'; length++)
if (!(isalnum(pos[length]) || pos[length] == '.'))
break;
/* Wind back so that the TLD part is checked correctly. */
for (trailing_dots = 0; trailing_dots < length; trailing_dots++)
if (!length || pos[length - trailing_dots - 1] != '.')
break;
/* Not perfect, but I am clueless as how to do better. Besides
* I don't really think it is an issue for ELinks. --jonas */
if (end_with_known_tld(pos, length - trailing_dots) != -1) {
warn = 1;
break;
}
pos += length;
while (*pos && (!isalnum(*pos) || *pos == '.'))
pos++;
}
mem_free(user);
return warn;
}
void
ses_goto(struct session *ses, struct uri *uri, char *target_frame,
struct location *target_location, cache_mode_T cache_mode,
enum task_type task_type, int redir)
{
/* [gettext_accelerator_context(ses_goto)] */
struct task *task;
int referrer_incomplete = 0;
int malicious_uri = 0;
int confirm_submit = uri->form && get_opt_bool("document.browse.forms"
".confirm_submit", ses);
char *m1 = NULL, *message = NULL;
struct memory_list *mlist = NULL;
if (ses->doc_view
&& ses->doc_view->document
&& ses->doc_view->document->refresh) {
kill_document_refresh(ses->doc_view->document->refresh);
}
assertm(!ses->loading_uri, "Buggy URI reference counting");
/* Reset the redirect counter if this is not a redirect. */
if (!redir) {
ses->redirect_cnt = 0;
}
/* Figure out whether to confirm submit or not */
/* Only confirm submit if we are posting form data or a misleading URI
* was detected. */
/* Note uri->post might be empty here but we are still supposely
* posting form data so this should be more correct. */
if (uri->user && uri->userlen
&& get_opt_bool("document.browse.links.warn_malicious", ses)
&& check_malicious_uri(uri)) {
malicious_uri = 1;
confirm_submit = 1;
} else if (uri->form) {
/* First check if the referring URI was incomplete. It
* indicates that the posted form data might be incomplete too.
* See bug 460. */
if (ses->referrer) {
struct cache_entry *cached;
cached = find_in_cache(ses->referrer);
referrer_incomplete = (cached && cached->incomplete);
}
if (referrer_incomplete) {
confirm_submit = 1;
} else if (get_validated_cache_entry(uri, cache_mode)) {
confirm_submit = 0;
}
}
if (!confirm_submit) {
ses_load(ses, get_uri_reference(uri), target_frame,
target_location, cache_mode, task_type);
return;
}
task = (struct task *)mem_alloc(sizeof(*task));
if (!task) return;
task->ses = ses;
task->uri = get_uri_reference(uri);
task->cache_mode = cache_mode;
task->session_task.type = task_type;
task->session_task.target.frame = null_or_stracpy(target_frame);
task->session_task.target.location = target_location;
if (malicious_uri) {
char *host = memacpy(uri->host, uri->hostlen);
char *user = memacpy(uri->user, uri->userlen);
char *uristring = get_uri_string(uri, URI_PUBLIC);
message = msg_text(ses->tab->term,
N_("The URL you are about to follow might be maliciously "
"crafted in order to confuse you. By following the URL "
"you will be connecting to host \"%s\" as user \"%s\".\n\n"
"Do you want to go to URL %s?"), host, user, uristring);
mem_free_if(host);
mem_free_if(user);
mem_free_if(uristring);
} else if (redir) {
m1 = N_("Do you want to follow the redirect and post form data "
"to URL %s?");
} else if (referrer_incomplete) {
m1 = N_("The form data you are about to post might be incomplete.\n"
"Do you want to post to URL %s?");
} else if (task_type == TASK_FORWARD) {
m1 = N_("Do you want to post form data to URL %s?");
} else {
m1 = N_("Do you want to repost form data to URL %s?");
}
if (!message && m1) {
char *uristring = get_uri_string(uri, URI_PUBLIC);
message = msg_text(ses->tab->term, m1, uristring);
mem_free_if(uristring);
}
add_to_ml(&mlist, task, (void *) NULL);
if (task->session_task.target.frame)
add_to_ml(&mlist, task->session_task.target.frame, (void *) NULL);
msg_box(ses->tab->term, mlist, MSGBOX_FREE_TEXT,
N_("Warning"), ALIGN_CENTER,
message,
task, 2,
MSG_BOX_BUTTON(N_("~Yes"), post_yes, B_ENTER),
MSG_BOX_BUTTON(N_("~No"), post_no, B_ESC));
}
/** If @a loaded_in_frame is set, this was called just to indicate a move inside
* a frameset, and we basically just reset the appropriate frame's view_state in
* that case. When clicking on a link inside a frame, the frame URI is somehow
* updated and added to the files-to-load queue, then ses_forward() is called
* with @a loaded_in_frame unset, duplicating the whole frameset's location,
* then later the file-to-load callback calls it for the particular frame with
* @a loaded_in_frame set. */
struct view_state *
ses_forward(struct session *ses, int loaded_in_frame)
{
struct location *loc = NULL;
struct view_state *vs;
if (!loaded_in_frame) {
free_files(ses);
if (get_opt_bool("document.browse.search.reset", NULL)) {
mem_free_set(&ses->search_word, NULL);
}
}
x:
if (!loaded_in_frame) {
loc = (struct location *)mem_calloc(1, sizeof(*loc));
if (!loc) return NULL;
copy_struct(&loc->download, &ses->loading);
}
if (ses->task.target.frame && *ses->task.target.frame) {
struct frame *frame;
assertm(have_location(ses), "no location yet");
if_assert_failed return NULL;
if (!loaded_in_frame) {
copy_location(loc, cur_loc(ses));
add_to_history(&ses->history, loc);
}
frame = ses_find_frame(ses, ses->task.target.frame);
if (!frame) {
frame = ses_find_iframe(ses, ses->task.target.frame);
}
if (!frame) {
if (!loaded_in_frame) {
del_from_history(&ses->history, loc);
destroy_location(loc);
}
mem_free_set(&ses->task.target.frame, NULL);
goto x;
}
vs = &frame->vs;
if (!loaded_in_frame) {
destroy_vs(vs, 1);
init_vs(vs, ses->loading_uri, vs->plain);
} else {
done_uri(vs->uri);
vs->uri = get_uri_reference(ses->loading_uri);
if (vs->doc_view) {
/* vs->doc_view itself will get detached in
* render_document_frames(), but that's too
* late for us. */
vs->doc_view->vs = NULL;
vs->doc_view = NULL;
}
#ifdef CONFIG_ECMASCRIPT
vs->ecmascript_fragile = 1;
#endif
}
} else {
assert(loc);
if_assert_failed return NULL;
init_list(loc->frames);
init_list(loc->iframes);
vs = &loc->vs;
init_vs(vs, ses->loading_uri, vs->plain);
add_to_history(&ses->history, loc);
}
ses->status.visited = 0;
/* This is another "branch" in the browsing, so throw away the current
* unhistory, we are venturing in another direction! */
if (ses->task.type == TASK_FORWARD)
clean_unhistory(&ses->history);
return vs;
}
static void
ses_imgmap(struct session *ses)
{
struct cache_entry *cached = find_in_cache(ses->loading_uri);
struct document_view *doc_view = current_frame(ses);
struct fragment *fragment;
struct memory_list *ml;
struct menu_item *menu;
if (!cached) {
INTERNAL("can't find cache entry");
return;
}
fragment = get_cache_fragment(cached);
if (!fragment) return;
if (!doc_view || !doc_view->document) return;
if (get_image_map(cached->head, fragment->data,
fragment->data + fragment->length,
&menu, &ml, ses->loading_uri,
&doc_view->document->options,
ses->task.target.frame,
get_terminal_codepage(ses->tab->term),
get_opt_codepage("document.codepage.assume", ses),
get_opt_bool("document.codepage.force_assumed", ses)))
return;
add_empty_window(ses->tab->term, (void (*)(void *)) freeml, ml);
do_menu(ses->tab->term, menu, ses, 0);
}
enum do_move {
DO_MOVE_ABORT,
DO_MOVE_DISPLAY,
DO_MOVE_DONE
};
static enum do_move
do_redirect(struct session *ses, struct download **download_p, struct cache_entry *cached)
{
enum task_type task = ses->task.type;
if (task == TASK_HISTORY && !have_location(ses))
return DO_MOVE_DISPLAY;
assertm(compare_uri(cached->uri, ses->loading_uri, URI_BASE),
"Redirecting using bad base URI");
if (cached->redirect->protocol == PROTOCOL_UNKNOWN)
return DO_MOVE_ABORT;
abort_loading(ses, 0);
if (have_location(ses))
*download_p = &cur_loc(ses)->download;
else
*download_p = NULL;
set_session_referrer(ses, cached->uri);
switch (task) {
case TASK_NONE:
break;
case TASK_FORWARD:
{
protocol_external_handler_T *fn;
struct uri *uri = cached->redirect;
fn = get_protocol_external_handler(ses->tab->term, uri);
if (fn) {
fn(ses, uri);
*download_p = NULL;
return DO_MOVE_ABORT;
}
}
/* Fall through. */
case TASK_IMGMAP:
ses_goto(ses, cached->redirect, ses->task.target.frame, NULL,
CACHE_MODE_NORMAL, task, 1);
return DO_MOVE_DONE;
case TASK_HISTORY:
ses_goto(ses, cached->redirect, NULL, ses->task.target.location,
CACHE_MODE_NORMAL, TASK_RELOAD, 1);
return DO_MOVE_DONE;
case TASK_RELOAD:
ses_goto(ses, cached->redirect, NULL, NULL,
ses->reloadlevel, TASK_RELOAD, 1);
return DO_MOVE_DONE;
}
return DO_MOVE_DISPLAY;
}
static enum do_move
do_move(struct session *ses, struct download **download_p)
{
struct cache_entry *cached;
assert(download_p && *download_p);
assertm(ses->loading_uri != NULL, "no ses->loading_uri");
if_assert_failed return DO_MOVE_ABORT;
if (ses->loading_uri->protocol == PROTOCOL_UNKNOWN)
return DO_MOVE_ABORT;
/* Handling image map needs to scan the source of the loaded document
* so all of it has to be available. */
if (ses->task.type == TASK_IMGMAP && is_in_progress_state((*download_p)->state))
return DO_MOVE_ABORT;
cached = (*download_p)->cached;
if (!cached) return DO_MOVE_ABORT;
if (cached->redirect && ses->redirect_cnt++ < MAX_REDIRECTS) {
enum do_move d = do_redirect(ses, download_p, cached);
if (d != DO_MOVE_DISPLAY) return d;
}
kill_timer(&ses->display_timer);
switch (ses->task.type) {
case TASK_NONE:
break;
case TASK_FORWARD:
if (setup_download_handler(ses, &ses->loading, cached, 0)) {
free_task(ses);
reload(ses, CACHE_MODE_NORMAL);
return DO_MOVE_DONE;
}
break;
case TASK_IMGMAP:
ses_imgmap(ses);
break;
case TASK_HISTORY:
ses_history_move(ses);
break;
case TASK_RELOAD:
ses->task.target.location = cur_loc(ses)->prev;
ses_history_move(ses);
ses_forward(ses, 0);
break;
}
if (is_in_progress_state((*download_p)->state)) {
if (have_location(ses))
*download_p = &cur_loc(ses)->download;
move_download(&ses->loading, *download_p, PRI_MAIN);
} else if (have_location(ses)) {
cur_loc(ses)->download.state = ses->loading.state;
}
free_task(ses);
return DO_MOVE_DISPLAY;
}
static void
loading_callback(struct download *download, struct session *ses)
{
enum do_move d;
assertm(ses->task.type, "loading_callback: no ses->task");
if_assert_failed return;
d = do_move(ses, &download);
if (!download) return;
if (d == DO_MOVE_DONE) goto end;
if (d == DO_MOVE_DISPLAY) {
download->callback = (download_callback_T *) doc_loading_callback;
download->data = ses;
display_timer(ses);
}
if (is_in_result_state(download->state)) {
if (ses->task.type) free_task(ses);
if (d == DO_MOVE_DISPLAY) doc_loading_callback(download, ses);
}
if (is_in_result_state(download->state)
&& !is_in_state(download->state, S_OK)) {
print_error_dialog(ses, download->state,
download->conn ? download->conn->uri : NULL,
download->pri);
if (d == DO_MOVE_ABORT) reload(ses, CACHE_MODE_NORMAL);
}
end:
check_questions_queue(ses);
print_screen_status_delayed(ses);
}
static void
do_follow_url(struct session *ses, struct uri *uri, char *target,
enum task_type task, cache_mode_T cache_mode, int do_referrer)
{
struct uri *referrer = NULL;
protocol_external_handler_T *external_handler;
if (!uri) {
print_error_dialog(ses, connection_state(S_BAD_URL), uri, PRI_CANCEL);
return;
}
external_handler = get_protocol_external_handler(ses->tab->term, uri);
if (external_handler) {
external_handler(ses, uri);
return;
}
if (do_referrer) {
struct document_view *doc_view = current_frame(ses);
if (doc_view && doc_view->document)
referrer = doc_view->document->uri;
}
if (target && !strcmp(target, "_blank")) {
int mode = get_opt_int("document.browse.links.target_blank",
ses);
if (mode == 3
&& !get_cmd_opt_bool("anonymous")
&& can_open_in_new(ses->tab->term)
&& !get_cmd_opt_bool("no-connect")
&& !get_cmd_opt_bool("no-home")) {
term_env_type_T env = ses->tab->term->environment;
open_uri_in_new_window(ses, uri, referrer, env,
cache_mode, task);
return;
}
if (mode > 0) {
struct session *new_ses;
new_ses = init_session(ses, ses->tab->term, uri, (mode == 2));
if (new_ses) ses = new_ses;
}
}
ses->reloadlevel = cache_mode;
if (ses->task.type == task) {
if (compare_uri(ses->loading_uri, uri, 0)) {
/* We're already loading the URL. */
return;
}
}
abort_loading(ses, 0);
set_session_referrer(ses, referrer);
ses_goto(ses, uri, target, NULL, cache_mode, task, 0);
}
static void
follow_url(struct session *ses, struct uri *uri, char *target,
enum task_type task, cache_mode_T cache_mode, int referrer)
{
#ifdef CONFIG_SCRIPTING
static int follow_url_event_id = EVENT_NONE;
char *uristring;
uristring = uri && !uri->post ? get_uri_string(uri, URI_BASE_FRAGMENT)
: NULL;
/* Do nothing if we do not have a URI or if it is a POST request
* because scripts can corrupt POST requests leading to bad
* things happening later on. */
if (!uristring) {
do_follow_url(ses, uri, target, task, cache_mode, referrer);
return;
}
set_event_id(follow_url_event_id, "follow-url");
trigger_event(follow_url_event_id, &uristring, ses);
if (!uristring || !*uristring) {
mem_free_if(uristring);
return;
}
/* FIXME: Compare if uristring and struri(uri) are equal */
/* FIXME: When uri->post will no longer be an encoded string (but
* hopefully some refcounted object) we will have to assign the post
* data object to the translated URI. */
uri = get_translated_uri(uristring, ses->tab->term->cwd);
mem_free(uristring);
#endif
do_follow_url(ses, uri, target, task, cache_mode, referrer);
#ifdef CONFIG_SCRIPTING
if (uri) done_uri(uri);
#endif
}
void
goto_uri(struct session *ses, struct uri *uri)
{
follow_url(ses, uri, NULL, TASK_FORWARD, CACHE_MODE_NORMAL, 0);
}
void
goto_uri_frame(struct session *ses, struct uri *uri,
char *target, cache_mode_T cache_mode)
{
follow_url(ses, uri, target, TASK_FORWARD, cache_mode, 1);
}
void
delayed_goto_uri_frame(void *data)
{
struct delayed_open *deo = (struct delayed_open *)data;
struct frame *frame;
assert(deo);
frame = deo->target ? ses_find_frame(deo->ses, deo->target) : NULL;
if (frame)
goto_uri_frame(deo->ses, deo->uri, frame->name, CACHE_MODE_NORMAL);
else {
goto_uri_frame(deo->ses, deo->uri, NULL, CACHE_MODE_NORMAL);
}
done_uri(deo->uri);
mem_free_if(deo->target);
mem_free(deo);
}
/* menu_func_T */
void
map_selected(struct terminal *term, void *ld_, void *ses_)
{
struct link_def *ld = (struct link_def *)ld_;
struct session *ses = (struct session *)ses_;
struct uri *uri = get_uri(ld->link, URI_NONE);
goto_uri_frame(ses, uri, ld->target, CACHE_MODE_NORMAL);
if (uri) done_uri(uri);
}
void
goto_url(struct session *ses, char *url)
{
struct uri *uri = get_uri(url, URI_NONE);
goto_uri(ses, uri);
if (uri) done_uri(uri);
}
struct uri *
get_hooked_uri(const char *uristring_, struct session *ses, char *cwd)
{
struct uri *uri;
char *uristring = stracpy(uristring_);
#if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
static int goto_url_event_id = EVENT_NONE;
if (!uristring) return NULL;
set_event_id(goto_url_event_id, "goto-url");
trigger_event(goto_url_event_id, &uristring, ses);
if (!uristring) return NULL;
#endif
uri = *uristring ? get_translated_uri(uristring, cwd) : NULL;
mem_free(uristring);
return uri;
}
void
goto_url_with_hook(struct session *ses, const char *url)
{
char *cwd = ses->tab->term->cwd;
struct uri *uri;
/* Bail out if passed empty string from goto-url dialog */
if (!*url) return;
uri = get_hooked_uri(url, ses, cwd);
if (!uri) return;
goto_uri(ses, uri);
done_uri(uri);
}
int
goto_url_home(struct session *ses)
{
const char *homepage = get_opt_str("ui.sessions.homepage", ses);
if (!*homepage) homepage = getenv("WWW_HOME");
if (!homepage || !*homepage) homepage = WWW_HOME_URL;
if (!homepage || !*homepage) return 0;
goto_url_with_hook(ses, homepage);
return 1;
}
/* TODO: Should there be goto_imgmap_reload() ? */
void
goto_imgmap(struct session *ses, struct uri *uri, char *target)
{
follow_url(ses, uri, target, TASK_IMGMAP, CACHE_MODE_NORMAL, 1);
}