762: Instead of setting a bare pointer for task.target.frame always
[elinks.git] / src / session / task.c
blobe2c4f67794a7f0b07e9c8e4b05f8a96f2bd1118c
1 /** Sessions task management
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
12 #include "elinks.h"
14 #include "bfu/menu.h"
15 #include "bfu/dialog.h"
16 #include "cache/cache.h"
17 #include "dialogs/menu.h"
18 #include "dialogs/status.h"
19 #include "document/document.h"
20 #include "document/html/parser.h"
21 #include "document/refresh.h"
22 #include "document/view.h"
23 #include "intl/gettext/libintl.h"
24 #include "main/event.h"
25 #include "main/timer.h"
26 #include "network/connection.h"
27 #include "osdep/newwin.h"
28 #include "protocol/protocol.h"
29 #include "protocol/uri.h"
30 #include "terminal/terminal.h"
31 #include "terminal/window.h"
32 #include "session/download.h"
33 #include "session/location.h"
34 #include "session/session.h"
35 #include "session/task.h"
36 #include "viewer/text/view.h"
39 static void loading_callback(struct download *, struct session *);
41 static void
42 free_task(struct session *ses)
44 assertm(ses->task.type, "Session has no task");
45 if_assert_failed return;
47 if (ses->loading_uri) {
48 done_uri(ses->loading_uri);
49 ses->loading_uri = NULL;
51 ses->task.type = TASK_NONE;
52 mem_free_set(&ses->task.target.frame, NULL);
55 void
56 abort_preloading(struct session *ses, int interrupt)
58 if (!ses->task.type) return;
60 cancel_download(&ses->loading, interrupt);
61 free_task(ses);
65 struct task {
66 struct session *ses;
67 struct uri *uri;
68 enum cache_mode cache_mode;
69 struct session_task session_task;
72 static void
73 ses_load(struct session *ses, struct uri *uri, unsigned char *target_frame,
74 struct location *target_location, enum cache_mode cache_mode,
75 enum task_type task_type)
77 ses->loading.callback = (download_callback_T *) loading_callback;
78 ses->loading.data = ses;
79 ses->loading_uri = uri;
81 ses->task.type = task_type;
82 mem_free_set(&ses->task.target.frame, null_or_stracpy(target_frame));
83 ses->task.target.location = target_location;
85 load_uri(ses->loading_uri, ses->referrer, &ses->loading,
86 PRI_MAIN, cache_mode, -1);
89 static void
90 post_yes(void *task_)
92 struct task *task = task_;
94 abort_preloading(task->ses, 0);
96 /* XXX: Make the session inherit the URI. */
97 ses_load(task->ses, task->uri, task->session_task.target.frame,
98 task->session_task.target.location, task->cache_mode,
99 task->session_task.type);
102 static void
103 post_no(void *task_)
105 struct task *task = task_;
107 reload(task->ses, CACHE_MODE_NORMAL);
108 done_uri(task->uri);
111 /** Check if the URI is obfuscated (bug 382). The problem is said to occur when
112 * a URI designed to pass access a specific location with a supplied username,
113 * contains misleading chars prior to the @ symbol.
115 * An attacker can exploit this issue by supplying a malicious URI pointing to
116 * a page designed to mimic that of a trusted site, and tricking a victim who
117 * follows a link into believing they are actually at the trusted location.
119 * Only the user ID (and not also the password) is checked because only the
120 * user ID is displayed in the status bar. */
121 static int
122 check_malicious_uri(struct uri *uri)
124 unsigned char *user, *pos;
125 int warn = 0;
127 assert(uri->user && uri->userlen);
129 user = pos = memacpy(uri->user, uri->userlen);
130 if (!user) return 0;
132 decode_uri_for_display(user);
134 while (*pos) {
135 int length, trailing_dots;
137 for (length = 0; pos[length] != '\0'; length++)
138 if (!(isalnum(pos[length]) || pos[length] == '.'))
139 break;
141 /* Wind back so that the TLD part is checked correctly. */
142 for (trailing_dots = 0; trailing_dots < length; trailing_dots++)
143 if (!length || pos[length - trailing_dots - 1] != '.')
144 break;
146 /* Not perfect, but I am clueless as how to do better. Besides
147 * I don't really think it is an issue for ELinks. --jonas */
148 if (end_with_known_tld(pos, length - trailing_dots) != -1) {
149 warn = 1;
150 break;
153 pos += length;
155 while (*pos && (!isalnum(*pos) || *pos == '.'))
156 pos++;
159 mem_free(user);
161 return warn;
164 void
165 ses_goto(struct session *ses, struct uri *uri, unsigned char *target_frame,
166 struct location *target_location, enum cache_mode cache_mode,
167 enum task_type task_type, int redir)
169 /* [gettext_accelerator_context(ses_goto)] */
170 struct task *task;
171 int referrer_incomplete = 0;
172 int malicious_uri = 0;
173 int confirm_submit = uri->form && get_opt_bool("document.browse.forms"
174 ".confirm_submit", ses);
175 unsigned char *m1 = NULL, *message = NULL;
177 if (ses->doc_view
178 && ses->doc_view->document
179 && ses->doc_view->document->refresh) {
180 kill_document_refresh(ses->doc_view->document->refresh);
183 assertm(!ses->loading_uri, "Buggy URI reference counting");
185 /* Reset the redirect counter if this is not a redirect. */
186 if (!redir) {
187 ses->redirect_cnt = 0;
190 /* Figure out whether to confirm submit or not */
192 /* Only confirm submit if we are posting form data or a misleading URI
193 * was detected. */
194 /* Note uri->post might be empty here but we are still supposely
195 * posting form data so this should be more correct. */
197 if (uri->user && uri->userlen
198 && get_opt_bool("document.browse.links.warn_malicious", ses)
199 && check_malicious_uri(uri)) {
200 malicious_uri = 1;
201 confirm_submit = 1;
203 } else if (uri->form) {
204 /* First check if the referring URI was incomplete. It
205 * indicates that the posted form data might be incomplete too.
206 * See bug 460. */
207 if (ses->referrer) {
208 struct cache_entry *cached;
210 cached = find_in_cache(ses->referrer);
211 referrer_incomplete = (cached && cached->incomplete);
214 if (referrer_incomplete) {
215 confirm_submit = 1;
217 } else if (get_validated_cache_entry(uri, cache_mode)) {
218 confirm_submit = 0;
222 if (!confirm_submit) {
223 ses_load(ses, get_uri_reference(uri), target_frame,
224 target_location, cache_mode, task_type);
225 return;
228 task = mem_alloc(sizeof(*task));
229 if (!task) return;
231 task->ses = ses;
232 task->uri = get_uri_reference(uri);
233 task->cache_mode = cache_mode;
234 task->session_task.type = task_type;
235 task->session_task.target.frame = null_or_stracpy(target_frame);
236 task->session_task.target.location = target_location;
238 if (malicious_uri) {
239 unsigned char *host = memacpy(uri->host, uri->hostlen);
240 unsigned char *user = memacpy(uri->user, uri->userlen);
241 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
243 message = msg_text(ses->tab->term,
244 N_("The URL you are about to follow might be maliciously "
245 "crafted in order to confuse you. By following the URL "
246 "you will be connecting to host \"%s\" as user \"%s\".\n\n"
247 "Do you want to go to URL %s?"), host, user, uristring);
249 mem_free_if(host);
250 mem_free_if(user);
251 mem_free_if(uristring);
253 } else if (redir) {
254 m1 = N_("Do you want to follow the redirect and post form data "
255 "to URL %s?");
257 } else if (referrer_incomplete) {
258 m1 = N_("The form data you are about to post might be incomplete.\n"
259 "Do you want to post to URL %s?");
261 } else if (task_type == TASK_FORWARD) {
262 m1 = N_("Do you want to post form data to URL %s?");
264 } else {
265 m1 = N_("Do you want to repost form data to URL %s?");
268 if (!message && m1) {
269 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
271 message = msg_text(ses->tab->term, m1, uristring);
272 mem_free_if(uristring);
275 msg_box(ses->tab->term, getml(task, (void *) NULL), MSGBOX_FREE_TEXT,
276 N_("Warning"), ALIGN_CENTER,
277 message,
278 task, 2,
279 MSG_BOX_BUTTON(N_("~Yes"), post_yes, B_ENTER),
280 MSG_BOX_BUTTON(N_("~No"), post_no, B_ESC));
284 /** If @a loaded_in_frame is set, this was called just to indicate a move inside
285 * a frameset, and we basically just reset the appropriate frame's view_state in
286 * that case. When clicking on a link inside a frame, the frame URI is somehow
287 * updated and added to the files-to-load queue, then ses_forward() is called
288 * with @a loaded_in_frame unset, duplicating the whole frameset's location,
289 * then later the file-to-load callback calls it for the particular frame with
290 * @a loaded_in_frame set. */
291 struct view_state *
292 ses_forward(struct session *ses, int loaded_in_frame)
294 struct location *loc = NULL;
295 struct view_state *vs;
297 if (!loaded_in_frame) {
298 free_files(ses);
299 mem_free_set(&ses->search_word, NULL);
303 if (!loaded_in_frame) {
304 loc = mem_calloc(1, sizeof(*loc));
305 if (!loc) return NULL;
306 copy_struct(&loc->download, &ses->loading);
309 if (ses->task.target.frame && *ses->task.target.frame) {
310 struct frame *frame;
312 assertm(have_location(ses), "no location yet");
313 if_assert_failed return NULL;
315 if (!loaded_in_frame) {
316 copy_location(loc, cur_loc(ses));
317 add_to_history(&ses->history, loc);
320 frame = ses_find_frame(ses, ses->task.target.frame);
321 if (!frame) {
322 if (!loaded_in_frame) {
323 del_from_history(&ses->history, loc);
324 destroy_location(loc);
326 mem_free_set(&ses->task.target.frame, NULL);
327 goto x;
330 vs = &frame->vs;
331 if (!loaded_in_frame) {
332 destroy_vs(vs, 1);
333 init_vs(vs, ses->loading_uri, vs->plain);
334 } else {
335 done_uri(vs->uri);
336 vs->uri = get_uri_reference(ses->loading_uri);
337 if (vs->doc_view) {
338 /* vs->doc_view itself will get detached in
339 * render_document_frames(), but that's too
340 * late for us. */
341 vs->doc_view->vs = NULL;
342 vs->doc_view = NULL;
344 #ifdef CONFIG_ECMASCRIPT
345 vs->ecmascript_fragile = 1;
346 #endif
349 } else {
350 assert(loc);
351 if_assert_failed return NULL;
353 init_list(loc->frames);
354 vs = &loc->vs;
355 init_vs(vs, ses->loading_uri, vs->plain);
356 add_to_history(&ses->history, loc);
359 ses->status.visited = 0;
361 /* This is another "branch" in the browsing, so throw away the current
362 * unhistory, we are venturing in another direction! */
363 if (ses->task.type == TASK_FORWARD)
364 clean_unhistory(&ses->history);
365 return vs;
368 static void
369 ses_imgmap(struct session *ses)
371 struct cache_entry *cached = find_in_cache(ses->loading_uri);
372 struct document_view *doc_view = current_frame(ses);
373 struct fragment *fragment;
374 struct memory_list *ml;
375 struct menu_item *menu;
377 if (!cached) {
378 INTERNAL("can't find cache entry");
379 return;
382 fragment = get_cache_fragment(cached);
383 if (!fragment) return;
385 if (!doc_view || !doc_view->document) return;
387 if (get_image_map(cached->head, fragment->data,
388 fragment->data + fragment->length,
389 &menu, &ml, ses->loading_uri,
390 &doc_view->document->options,
391 ses->task.target.frame,
392 get_terminal_codepage(ses->tab->term),
393 get_opt_codepage("document.codepage.assume", ses),
394 get_opt_bool("document.codepage.force_assumed", ses)))
395 return;
397 add_empty_window(ses->tab->term, (void (*)(void *)) freeml, ml);
398 do_menu(ses->tab->term, menu, ses, 0);
401 enum do_move {
402 DO_MOVE_ABORT,
403 DO_MOVE_DISPLAY,
404 DO_MOVE_DONE
407 static enum do_move
408 do_redirect(struct session *ses, struct download **download_p, struct cache_entry *cached)
410 enum task_type task = ses->task.type;
412 if (task == TASK_HISTORY && !have_location(ses))
413 return DO_MOVE_DISPLAY;
415 assertm(compare_uri(cached->uri, ses->loading_uri, URI_BASE),
416 "Redirecting using bad base URI");
418 if (cached->redirect->protocol == PROTOCOL_UNKNOWN)
419 return DO_MOVE_ABORT;
421 abort_loading(ses, 0);
422 if (have_location(ses))
423 *download_p = &cur_loc(ses)->download;
424 else
425 *download_p = NULL;
427 set_session_referrer(ses, cached->uri);
429 switch (task) {
430 case TASK_NONE:
431 break;
432 case TASK_FORWARD:
434 protocol_external_handler_T *fn;
435 struct uri *uri = cached->redirect;
437 fn = get_protocol_external_handler(ses->tab->term, uri);
438 if (fn) {
439 fn(ses, uri);
440 *download_p = NULL;
441 return DO_MOVE_ABORT;
444 /* Fall through. */
445 case TASK_IMGMAP:
446 ses_goto(ses, cached->redirect, ses->task.target.frame, NULL,
447 CACHE_MODE_NORMAL, task, 1);
448 return DO_MOVE_DONE;
449 case TASK_HISTORY:
450 ses_goto(ses, cached->redirect, NULL, ses->task.target.location,
451 CACHE_MODE_NORMAL, TASK_RELOAD, 1);
452 return DO_MOVE_DONE;
453 case TASK_RELOAD:
454 ses_goto(ses, cached->redirect, NULL, NULL,
455 ses->reloadlevel, TASK_RELOAD, 1);
456 return DO_MOVE_DONE;
459 return DO_MOVE_DISPLAY;
462 static enum do_move
463 do_move(struct session *ses, struct download **download_p)
465 struct cache_entry *cached;
467 assert(download_p && *download_p);
468 assertm(ses->loading_uri != NULL, "no ses->loading_uri");
469 if_assert_failed return DO_MOVE_ABORT;
471 if (ses->loading_uri->protocol == PROTOCOL_UNKNOWN)
472 return DO_MOVE_ABORT;
474 /* Handling image map needs to scan the source of the loaded document
475 * so all of it has to be available. */
476 if (ses->task.type == TASK_IMGMAP && is_in_progress_state((*download_p)->state))
477 return DO_MOVE_ABORT;
479 cached = (*download_p)->cached;
480 if (!cached) return DO_MOVE_ABORT;
482 if (cached->redirect && ses->redirect_cnt++ < MAX_REDIRECTS) {
483 enum do_move d = do_redirect(ses, download_p, cached);
485 if (d != DO_MOVE_DISPLAY) return d;
488 kill_timer(&ses->display_timer);
490 switch (ses->task.type) {
491 case TASK_NONE:
492 break;
493 case TASK_FORWARD:
494 if (setup_download_handler(ses, &ses->loading, cached, 0)) {
495 free_task(ses);
496 reload(ses, CACHE_MODE_NORMAL);
497 return DO_MOVE_DONE;
499 break;
500 case TASK_IMGMAP:
501 ses_imgmap(ses);
502 break;
503 case TASK_HISTORY:
504 ses_history_move(ses);
505 break;
506 case TASK_RELOAD:
507 ses->task.target.location = cur_loc(ses)->prev;
508 ses_history_move(ses);
509 ses_forward(ses, 0);
510 break;
513 if (is_in_progress_state((*download_p)->state)) {
514 if (have_location(ses))
515 *download_p = &cur_loc(ses)->download;
516 move_download(&ses->loading, *download_p, PRI_MAIN);
517 } else if (have_location(ses)) {
518 cur_loc(ses)->download.state = ses->loading.state;
521 free_task(ses);
522 return DO_MOVE_DISPLAY;
525 static void
526 loading_callback(struct download *download, struct session *ses)
528 enum do_move d;
530 assertm(ses->task.type, "loading_callback: no ses->task");
531 if_assert_failed return;
533 d = do_move(ses, &download);
534 if (!download) return;
535 if (d == DO_MOVE_DONE) goto end;
537 if (d == DO_MOVE_DISPLAY) {
538 download->callback = (download_callback_T *) doc_loading_callback;
539 display_timer(ses);
542 if (is_in_result_state(download->state)) {
543 if (ses->task.type) free_task(ses);
544 if (d == DO_MOVE_DISPLAY) doc_loading_callback(download, ses);
547 if (is_in_result_state(download->state)
548 && !is_in_state(download->state, S_OK)) {
549 print_error_dialog(ses, download->state,
550 download->conn ? download->conn->uri : NULL,
551 download->pri);
552 if (d == DO_MOVE_ABORT) reload(ses, CACHE_MODE_NORMAL);
555 end:
556 check_questions_queue(ses);
557 print_screen_status(ses);
561 static void
562 do_follow_url(struct session *ses, struct uri *uri, unsigned char *target,
563 enum task_type task, enum cache_mode cache_mode, int do_referrer)
565 struct uri *referrer = NULL;
566 protocol_external_handler_T *external_handler;
568 if (!uri) {
569 print_error_dialog(ses, connection_state(S_BAD_URL), uri, PRI_CANCEL);
570 return;
573 external_handler = get_protocol_external_handler(ses->tab->term, uri);
574 if (external_handler) {
575 external_handler(ses, uri);
576 return;
579 if (do_referrer) {
580 struct document_view *doc_view = current_frame(ses);
582 if (doc_view && doc_view->document)
583 referrer = doc_view->document->uri;
586 if (target && !strcmp(target, "_blank")) {
587 int mode = get_opt_int("document.browse.links.target_blank",
588 ses);
590 if (mode == 3
591 && !get_cmd_opt_bool("anonymous")
592 && can_open_in_new(ses->tab->term)
593 && !get_cmd_opt_bool("no-connect")
594 && !get_cmd_opt_bool("no-home")) {
595 enum term_env_type env = ses->tab->term->environment;
597 open_uri_in_new_window(ses, uri, referrer, env,
598 cache_mode, task);
599 return;
602 if (mode > 0) {
603 struct session *new_ses;
605 new_ses = init_session(ses, ses->tab->term, uri, (mode == 2));
606 if (new_ses) ses = new_ses;
610 ses->reloadlevel = cache_mode;
612 if (ses->task.type == task) {
613 if (compare_uri(ses->loading_uri, uri, 0)) {
614 /* We're already loading the URL. */
615 return;
619 abort_loading(ses, 0);
621 set_session_referrer(ses, referrer);
623 ses_goto(ses, uri, target, NULL, cache_mode, task, 0);
626 static void
627 follow_url(struct session *ses, struct uri *uri, unsigned char *target,
628 enum task_type task, enum cache_mode cache_mode, int referrer)
630 #ifdef CONFIG_SCRIPTING
631 static int follow_url_event_id = EVENT_NONE;
632 unsigned char *uristring = uri ? get_uri_string(uri, URI_BASE | URI_FRAGMENT) : NULL;
634 if (!uristring) {
635 do_follow_url(ses, uri, target, task, cache_mode, referrer);
636 return;
639 set_event_id(follow_url_event_id, "follow-url");
640 trigger_event(follow_url_event_id, &uristring, ses);
642 if (!uristring || !*uristring) {
643 mem_free_if(uristring);
644 return;
647 /* FIXME: Compare if uristring and struri(uri) are equal */
648 /* FIXME: When uri->post will no longer be an encoded string (but
649 * hopefully some refcounted object) we will have to assign the post
650 * data object to the translated URI. */
651 uri = get_translated_uri(uristring, ses->tab->term->cwd);
652 mem_free(uristring);
653 #endif
655 do_follow_url(ses, uri, target, task, cache_mode, referrer);
657 #ifdef CONFIG_SCRIPTING
658 if (uri) done_uri(uri);
659 #endif
662 void
663 goto_uri(struct session *ses, struct uri *uri)
665 follow_url(ses, uri, NULL, TASK_FORWARD, CACHE_MODE_NORMAL, 0);
668 void
669 goto_uri_frame(struct session *ses, struct uri *uri,
670 unsigned char *target, enum cache_mode cache_mode)
672 follow_url(ses, uri, target, TASK_FORWARD, cache_mode, 1);
675 void
676 delayed_goto_uri_frame(void *data)
678 struct delayed_open *deo = data;
679 struct frame *frame;
681 assert(deo);
682 frame = ses_find_frame(deo->ses, deo->target);
683 if (frame)
684 goto_uri_frame(deo->ses, deo->uri, frame->name, CACHE_MODE_NORMAL);
685 else {
686 goto_uri_frame(deo->ses, deo->uri, NULL, CACHE_MODE_NORMAL);
688 done_uri(deo->uri);
689 mem_free(deo->target);
690 mem_free(deo);
693 /* menu_func_T */
694 void
695 map_selected(struct terminal *term, void *ld_, void *ses_)
697 struct link_def *ld = ld_;
698 struct session *ses = ses_;
699 struct uri *uri = get_uri(ld->link, 0);
701 goto_uri_frame(ses, uri, ld->target, CACHE_MODE_NORMAL);
702 if (uri) done_uri(uri);
706 void
707 goto_url(struct session *ses, unsigned char *url)
709 struct uri *uri = get_uri(url, 0);
711 goto_uri(ses, uri);
712 if (uri) done_uri(uri);
715 struct uri *
716 get_hooked_uri(unsigned char *uristring, struct session *ses, unsigned char *cwd)
718 struct uri *uri;
720 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
721 static int goto_url_event_id = EVENT_NONE;
723 uristring = stracpy(uristring);
724 if (!uristring) return NULL;
726 set_event_id(goto_url_event_id, "goto-url");
727 trigger_event(goto_url_event_id, &uristring, ses);
728 if (!uristring) return NULL;
729 #endif
731 uri = *uristring ? get_translated_uri(uristring, cwd) : NULL;
733 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
734 mem_free(uristring);
735 #endif
736 return uri;
739 void
740 goto_url_with_hook(struct session *ses, unsigned char *url)
742 unsigned char *cwd = ses->tab->term->cwd;
743 struct uri *uri;
745 /* Bail out if passed empty string from goto-url dialog */
746 if (!*url) return;
748 uri = get_hooked_uri(url, ses, cwd);
749 if (!uri) return;
751 goto_uri(ses, uri);
753 done_uri(uri);
757 goto_url_home(struct session *ses)
759 unsigned char *homepage = get_opt_str("ui.sessions.homepage", ses);
761 if (!*homepage) homepage = getenv("WWW_HOME");
762 if (!homepage || !*homepage) homepage = WWW_HOME_URL;
764 if (!homepage || !*homepage) return 0;
766 goto_url_with_hook(ses, homepage);
767 return 1;
770 /* TODO: Should there be goto_imgmap_reload() ? */
772 void
773 goto_imgmap(struct session *ses, struct uri *uri, unsigned char *target)
775 follow_url(ses, uri, target, TASK_IMGMAP, CACHE_MODE_NORMAL, 1);