Initial commit of the HEAD branch of the ELinks CVS repository, as of
[elinks/images.git] / src / session / task.c
blobc475be93018002a8d7ca6d9554ffe0a2d5188db0
1 /* Sessions task management */
2 /* $Id: task.c,v 1.183 2005/09/06 14:16:48 witekfl Exp $ */
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;
54 void
55 abort_preloading(struct session *ses, int interrupt)
57 if (!ses->task.type) return;
59 change_connection(&ses->loading, NULL, PRI_CANCEL, interrupt);
60 free_task(ses);
64 struct task {
65 struct session *ses;
66 struct uri *uri;
67 enum cache_mode cache_mode;
68 struct session_task session_task;
71 static void
72 post_yes(struct task *task)
74 struct session *ses = task->ses;
76 abort_preloading(task->ses, 0);
78 ses->loading.callback = (download_callback_T *) loading_callback;
79 ses->loading.data = task->ses;
80 ses->loading_uri = task->uri; /* XXX: Make the session inherit the URI. */
81 memcpy(&ses->task, &task->session_task, sizeof(ses->task));
83 load_uri(ses->loading_uri, ses->referrer, &ses->loading,
84 PRI_MAIN, task->cache_mode, -1);
87 static void
88 post_no(struct task *task)
90 reload(task->ses, CACHE_MODE_NORMAL);
91 done_uri(task->uri);
94 /* Check if the URI is obfuscated (bug 382). The problem is said to occur when
95 * a URI designed to pass access a specific location with a supplied username,
96 * contains misleading chars prior to the @ symbol.
98 * An attacker can exploit this issue by supplying a malicious URI pointing to
99 * a page designed to mimic that of a trusted site, and tricking a victim who
100 * follows a link into believing they are actually at the trusted location.
102 * Only the user ID (and not also the password) is checked because only the
103 * user ID is displayed in the status bar. */
104 static int
105 check_malicious_uri(struct uri *uri)
107 unsigned char *user, *pos;
108 int warn = 0;
110 assert(uri->user && uri->userlen);
112 user = pos = memacpy(uri->user, uri->userlen);
113 if (!user) return 0;
115 decode_uri_for_display(user);
117 while (*pos) {
118 int length, trailing_dots;
120 for (length = 0; pos[length] != '\0'; length++)
121 if (!(isalnum(pos[length]) || pos[length] == '.'))
122 break;
124 /* Wind back so that the TLD part is checked correctly. */
125 for (trailing_dots = 0; trailing_dots < length; trailing_dots++)
126 if (!length || pos[length - trailing_dots - 1] != '.')
127 break;
129 /* Not perfect, but I am clueless as how to do better. Besides
130 * I don't really think it is an issue for ELinks. --jonas */
131 if (end_with_known_tld(pos, length - trailing_dots) != -1) {
132 warn = 1;
133 break;
136 pos += length;
138 while (*pos && (!isalnum(*pos) || *pos == '.'))
139 pos++;
142 mem_free(user);
144 return warn;
147 void
148 ses_goto(struct session *ses, struct uri *uri, unsigned char *target_frame,
149 struct location *target_location, enum cache_mode cache_mode,
150 enum task_type task_type, int redir)
152 struct task *task;
153 int referrer_incomplete = 0;
154 int malicious_uri = 0;
155 int confirm_submit = uri->form;
156 unsigned char *m1 = NULL, *message = NULL;
158 if (ses->doc_view
159 && ses->doc_view->document
160 && ses->doc_view->document->refresh) {
161 kill_document_refresh(ses->doc_view->document->refresh);
164 assertm(!ses->loading_uri, "Buggy URI reference counting");
166 /* Reset the redirect counter if this is not a redirect. */
167 if (!redir) {
168 ses->redirect_cnt = 0;
171 /* Figure out whether to confirm submit or not */
173 /* Only confirm submit if we are posting form data or a misleading URI
174 * was detected. */
175 /* Note uri->post might be empty here but we are still supposely
176 * posting form data so this should be more correct. */
178 if (uri->user && uri->userlen
179 && get_opt_bool("document.browse.links.warn_malicious")
180 && check_malicious_uri(uri)) {
181 malicious_uri = 1;
182 confirm_submit = 1;
184 } else if (!uri->form) {
185 confirm_submit = 0;
187 } else {
188 struct cache_entry *cached;
190 /* First check if the referring URI was incomplete. It
191 * indicates that the posted form data might be incomplete too.
192 * See bug 460. */
193 if (ses->referrer) {
194 cached = find_in_cache(ses->referrer);
195 referrer_incomplete = (cached && cached->incomplete);
198 if (!get_opt_bool("document.browse.forms.confirm_submit")
199 && !referrer_incomplete) {
200 confirm_submit = 0;
202 } else if (get_validated_cache_entry(uri, cache_mode)) {
203 confirm_submit = 0;
207 if (!confirm_submit) {
208 ses->loading.callback = (download_callback_T *) loading_callback;
209 ses->loading.data = ses;
210 ses->loading_uri = get_uri_reference(uri);
212 ses->task.type = task_type;
213 ses->task.target.frame = target_frame;
214 ses->task.target.location = target_location;
216 load_uri(ses->loading_uri, ses->referrer, &ses->loading,
217 PRI_MAIN, cache_mode, -1);
219 return;
222 task = mem_alloc(sizeof(*task));
223 if (!task) return;
225 task->ses = ses;
226 task->uri = get_uri_reference(uri);
227 task->cache_mode = cache_mode;
228 task->session_task.type = task_type;
229 task->session_task.target.frame = target_frame;
230 task->session_task.target.location = target_location;
232 if (malicious_uri) {
233 unsigned char *host = memacpy(uri->host, uri->hostlen);
234 unsigned char *user = memacpy(uri->user, uri->userlen);
235 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
237 message = msg_text(ses->tab->term,
238 N_("The URL you are about to follow might be maliciously "
239 "crafted in order to confuse you. By following the URL "
240 "you will be connecting to host \"%s\" as user \"%s\".\n\n"
241 "Do you want to go to URL %s?"), host, user, uristring);
243 mem_free_if(host);
244 mem_free_if(user);
245 mem_free_if(uristring);
247 } else if (redir) {
248 m1 = N_("Do you want to follow the redirect and post form data "
249 "to URL %s?");
251 } else if (referrer_incomplete) {
252 m1 = N_("The form data you are about to post might be incomplete.\n"
253 "Do you want to post to URL %s?");
255 } else if (task_type == TASK_FORWARD) {
256 m1 = N_("Do you want to post form data to URL %s?");
258 } else {
259 m1 = N_("Do you want to repost form data to URL %s?");
262 if (!message && m1) {
263 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
265 message = msg_text(ses->tab->term, m1, uristring);
266 mem_free_if(uristring);
269 msg_box(ses->tab->term, getml(task, NULL), MSGBOX_FREE_TEXT,
270 N_("Warning"), ALIGN_CENTER,
271 message,
272 task, 2,
273 N_("~Yes"), post_yes, B_ENTER,
274 N_("~No"), post_no, B_ESC);
278 /* If @loaded_in_frame is set, this was called just to indicate a move inside a
279 * frameset, and we basically just reset the appropriate frame's view_state in
280 * that case. When clicking on a link inside a frame, the frame URI is somehow
281 * updated and added to the files-to-load queue, then ses_forward() is called
282 * with @loaded_in_frame unset, duplicating the whole frameset's location, then
283 * later the file-to-load callback calls it for the particular frame with
284 * @loaded_in_frame set. */
285 struct view_state *
286 ses_forward(struct session *ses, int loaded_in_frame)
288 struct location *loc = NULL;
289 struct view_state *vs;
291 if (!loaded_in_frame) {
292 free_files(ses);
293 mem_free_set(&ses->search_word, NULL);
297 if (!loaded_in_frame) {
298 loc = mem_calloc(1, sizeof(*loc));
299 if (!loc) return NULL;
300 copy_struct(&loc->download, &ses->loading);
303 if (ses->task.target.frame && *ses->task.target.frame) {
304 struct frame *frame;
306 assertm(have_location(ses), "no location yet");
307 if_assert_failed return NULL;
309 if (!loaded_in_frame) {
310 copy_location(loc, cur_loc(ses));
311 add_to_history(&ses->history, loc);
314 frame = ses_find_frame(ses, ses->task.target.frame);
315 if (!frame) {
316 if (!loaded_in_frame) {
317 del_from_history(&ses->history, loc);
318 destroy_location(loc);
320 ses->task.target.frame = NULL;
321 goto x;
324 vs = &frame->vs;
325 if (!loaded_in_frame) {
326 destroy_vs(vs, 1);
327 init_vs(vs, ses->loading_uri, vs->plain);
328 } else {
329 done_uri(vs->uri);
330 vs->uri = get_uri_reference(ses->loading_uri);
331 if (vs->doc_view) {
332 /* vs->doc_view itself will get detached in
333 * render_document_frames(), but that's too
334 * late for us. */
335 vs->doc_view->vs = NULL;
336 vs->doc_view = NULL;
338 #ifdef CONFIG_ECMASCRIPT
339 vs->ecmascript_fragile = 1;
340 #endif
343 } else {
344 assert(loc);
345 if_assert_failed return NULL;
347 init_list(loc->frames);
348 vs = &loc->vs;
349 init_vs(vs, ses->loading_uri, vs->plain);
350 add_to_history(&ses->history, loc);
353 ses->status.visited = 0;
355 /* This is another "branch" in the browsing, so throw away the current
356 * unhistory, we are venturing in another direction! */
357 if (ses->task.type == TASK_FORWARD)
358 clean_unhistory(&ses->history);
359 return vs;
362 static void
363 ses_imgmap(struct session *ses)
365 struct cache_entry *cached = find_in_cache(ses->loading_uri);
366 struct document_view *doc_view = current_frame(ses);
367 struct fragment *fragment;
368 struct memory_list *ml;
369 struct menu_item *menu;
371 if (!cached) {
372 INTERNAL("can't find cache entry");
373 return;
376 fragment = get_cache_fragment(cached);
377 if (!fragment) return;
379 if (!doc_view || !doc_view->document) return;
381 if (get_image_map(cached->head, fragment->data,
382 fragment->data + fragment->length,
383 &menu, &ml, ses->loading_uri,
384 &doc_view->document->options,
385 ses->task.target.frame,
386 get_opt_codepage_tree(ses->tab->term->spec, "charset"),
387 get_opt_codepage("document.codepage.assume"),
388 get_opt_bool("document.codepage.force_assumed")))
389 return;
391 add_empty_window(ses->tab->term, (void (*)(void *)) freeml, ml);
392 do_menu(ses->tab->term, menu, ses, 0);
395 enum do_move {
396 DO_MOVE_ABORT,
397 DO_MOVE_DISPLAY,
398 DO_MOVE_DONE
401 static enum do_move
402 do_redirect(struct session *ses, struct download **download_p, struct cache_entry *cached)
404 enum task_type task = ses->task.type;
406 if (task == TASK_HISTORY && !have_location(ses))
407 return DO_MOVE_DISPLAY;
409 assertm(compare_uri(cached->uri, ses->loading_uri, URI_BASE),
410 "Redirecting using bad base URI");
412 if (cached->redirect->protocol == PROTOCOL_UNKNOWN)
413 return DO_MOVE_ABORT;
415 abort_loading(ses, 0);
416 if (have_location(ses))
417 *download_p = &cur_loc(ses)->download;
418 else
419 *download_p = NULL;
421 set_session_referrer(ses, cached->uri);
423 switch (task) {
424 case TASK_NONE:
425 break;
426 case TASK_FORWARD:
428 protocol_external_handler_T *fn;
429 struct uri *uri = cached->redirect;
431 fn = get_protocol_external_handler(ses->tab->term, uri);
432 if (fn) {
433 fn(ses, uri);
434 *download_p = NULL;
435 return DO_MOVE_ABORT;
438 /* Fall through. */
439 case TASK_IMGMAP:
440 ses_goto(ses, cached->redirect, ses->task.target.frame, NULL,
441 CACHE_MODE_NORMAL, task, 1);
442 return DO_MOVE_DONE;
443 case TASK_HISTORY:
444 ses_goto(ses, cached->redirect, NULL, ses->task.target.location,
445 CACHE_MODE_NORMAL, TASK_RELOAD, 1);
446 return DO_MOVE_DONE;
447 case TASK_RELOAD:
448 ses_goto(ses, cached->redirect, NULL, NULL,
449 ses->reloadlevel, TASK_RELOAD, 1);
450 return DO_MOVE_DONE;
453 return DO_MOVE_DISPLAY;
456 static enum do_move
457 do_move(struct session *ses, struct download **download_p)
459 struct cache_entry *cached;
461 assert(download_p && *download_p);
462 assertm(ses->loading_uri, "no ses->loading_uri");
463 if_assert_failed return DO_MOVE_ABORT;
465 if (ses->loading_uri->protocol == PROTOCOL_UNKNOWN)
466 return DO_MOVE_ABORT;
468 /* Handling image map needs to scan the source of the loaded document
469 * so all of it has to be available. */
470 if (ses->task.type == TASK_IMGMAP && is_in_progress_state((*download_p)->state))
471 return DO_MOVE_ABORT;
473 cached = (*download_p)->cached;
474 if (!cached) return DO_MOVE_ABORT;
476 if (cached->redirect && ses->redirect_cnt++ < MAX_REDIRECTS) {
477 enum do_move d = do_redirect(ses, download_p, cached);
479 if (d != DO_MOVE_DISPLAY) return d;
482 kill_timer(&ses->display_timer);
484 switch (ses->task.type) {
485 case TASK_NONE:
486 break;
487 case TASK_FORWARD:
488 if (setup_download_handler(ses, &ses->loading, cached, 0)) {
489 free_task(ses);
490 reload(ses, CACHE_MODE_NORMAL);
491 return DO_MOVE_DONE;
493 break;
494 case TASK_IMGMAP:
495 ses_imgmap(ses);
496 break;
497 case TASK_HISTORY:
498 ses_history_move(ses);
499 break;
500 case TASK_RELOAD:
501 ses->task.target.location = cur_loc(ses)->prev;
502 ses_history_move(ses);
503 ses_forward(ses, 0);
504 break;
507 if (is_in_progress_state((*download_p)->state)) {
508 if (have_location(ses))
509 *download_p = &cur_loc(ses)->download;
510 change_connection(&ses->loading, *download_p, PRI_MAIN, 0);
511 } else if (have_location(ses)) {
512 cur_loc(ses)->download.state = ses->loading.state;
515 free_task(ses);
516 return DO_MOVE_DISPLAY;
519 static void
520 loading_callback(struct download *download, struct session *ses)
522 enum do_move d;
524 assertm(ses->task.type, "loading_callback: no ses->task");
525 if_assert_failed return;
527 d = do_move(ses, &download);
528 if (!download) return;
529 if (d == DO_MOVE_DONE) goto end;
531 if (d == DO_MOVE_DISPLAY) {
532 download->callback = (download_callback_T *) doc_loading_callback;
533 display_timer(ses);
536 if (is_in_result_state(download->state)) {
537 if (ses->task.type) free_task(ses);
538 if (d == DO_MOVE_DISPLAY) doc_loading_callback(download, ses);
541 if (is_in_result_state(download->state) && download->state != S_OK) {
542 print_error_dialog(ses, download->state, download->conn->uri,
543 download->pri);
544 if (d == DO_MOVE_ABORT) reload(ses, CACHE_MODE_NORMAL);
547 end:
548 check_questions_queue(ses);
549 print_screen_status(ses);
553 static void
554 do_follow_url(struct session *ses, struct uri *uri, unsigned char *target,
555 enum task_type task, enum cache_mode cache_mode, int do_referrer)
557 struct uri *referrer = NULL;
558 protocol_external_handler_T *external_handler;
560 if (!uri) {
561 print_error_dialog(ses, S_BAD_URL, uri, PRI_CANCEL);
562 return;
565 external_handler = get_protocol_external_handler(ses->tab->term, uri);
566 if (external_handler) {
567 external_handler(ses, uri);
568 return;
571 if (do_referrer) {
572 struct document_view *doc_view = current_frame(ses);
574 if (doc_view && doc_view->document)
575 referrer = doc_view->document->uri;
578 if (target && !strcmp(target, "_blank")) {
579 int mode = get_opt_int("document.browse.links.target_blank");
581 if (mode == 3
582 && !get_cmd_opt_bool("anonymous")
583 && can_open_in_new(ses->tab->term)
584 && !get_cmd_opt_bool("no-connect")
585 && !get_cmd_opt_bool("no-home")) {
586 enum term_env_type env = ses->tab->term->environment;
588 open_uri_in_new_window(ses, uri, referrer, env,
589 cache_mode, task);
590 return;
593 if (mode > 0) {
594 struct session *new_ses;
596 new_ses = init_session(ses, ses->tab->term, uri, (mode == 2));
597 if (new_ses) ses = new_ses;
601 ses->reloadlevel = cache_mode;
603 if (ses->task.type == task) {
604 if (compare_uri(ses->loading_uri, uri, 0)) {
605 /* We're already loading the URL. */
606 return;
610 abort_loading(ses, 0);
612 set_session_referrer(ses, referrer);
614 ses_goto(ses, uri, target, NULL, cache_mode, task, 0);
617 static void
618 follow_url(struct session *ses, struct uri *uri, unsigned char *target,
619 enum task_type task, enum cache_mode cache_mode, int referrer)
621 #ifdef CONFIG_SCRIPTING
622 static int follow_url_event_id = EVENT_NONE;
623 unsigned char *uristring = uri ? get_uri_string(uri, URI_BASE | URI_FRAGMENT) : NULL;
625 if (!uristring) {
626 do_follow_url(ses, uri, target, task, cache_mode, referrer);
627 return;
630 set_event_id(follow_url_event_id, "follow-url");
631 trigger_event(follow_url_event_id, &uristring, ses);
633 if (!uristring || !*uristring) {
634 mem_free_if(uristring);
635 return;
638 /* FIXME: Compare if uristring and struri(uri) are equal */
639 /* FIXME: When uri->post will no longer be an encoded string (but
640 * hopefully some refcounted object) we will have to assign the post
641 * data object to the translated URI. */
642 uri = get_translated_uri(uristring, ses->tab->term->cwd);
643 mem_free(uristring);
644 #endif
646 do_follow_url(ses, uri, target, task, cache_mode, referrer);
648 #ifdef CONFIG_SCRIPTING
649 if (uri) done_uri(uri);
650 #endif
653 void
654 goto_uri(struct session *ses, struct uri *uri)
656 follow_url(ses, uri, NULL, TASK_FORWARD, CACHE_MODE_NORMAL, 0);
659 void
660 goto_uri_frame(struct session *ses, struct uri *uri,
661 unsigned char *target, enum cache_mode cache_mode)
663 follow_url(ses, uri, target, TASK_FORWARD, cache_mode, 1);
666 /* menu_func_T */
667 void
668 map_selected(struct terminal *term, void *ld_, void *ses_)
670 struct link_def *ld = ld_;
671 struct session *ses = ses_;
672 struct uri *uri = get_uri(ld->link, 0);
674 goto_uri_frame(ses, uri, ld->target, CACHE_MODE_NORMAL);
675 if (uri) done_uri(uri);
679 void
680 goto_url(struct session *ses, unsigned char *url)
682 struct uri *uri = get_uri(url, 0);
684 goto_uri(ses, uri);
685 if (uri) done_uri(uri);
688 struct uri *
689 get_hooked_uri(unsigned char *uristring, struct session *ses, unsigned char *cwd)
691 struct uri *uri;
693 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
694 static int goto_url_event_id = EVENT_NONE;
696 uristring = stracpy(uristring);
697 if (!uristring) return NULL;
699 set_event_id(goto_url_event_id, "goto-url");
700 trigger_event(goto_url_event_id, &uristring, ses);
701 if (!uristring) return NULL;
702 #endif
704 uri = *uristring ? get_translated_uri(uristring, cwd) : NULL;
706 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
707 mem_free(uristring);
708 #endif
709 return uri;
712 void
713 goto_url_with_hook(struct session *ses, unsigned char *url)
715 unsigned char *cwd = ses->tab->term->cwd;
716 struct uri *uri;
718 /* Bail out if passed empty string from goto-url dialog */
719 if (!*url) return;
721 uri = get_hooked_uri(url, ses, cwd);
722 goto_uri(ses, uri);
723 if (uri) done_uri(uri);
727 goto_url_home(struct session *ses)
729 unsigned char *homepage = get_opt_str("ui.sessions.homepage");
731 if (!*homepage) homepage = getenv("WWW_HOME");
732 if (!homepage || !*homepage) homepage = WWW_HOME_URL;
734 if (!homepage || !*homepage) return 0;
736 goto_url_with_hook(ses, homepage);
737 return 1;
740 /* TODO: Should there be goto_imgmap_reload() ? */
742 void
743 goto_imgmap(struct session *ses, struct uri *uri, unsigned char *target)
745 follow_url(ses, uri, target, TASK_IMGMAP, CACHE_MODE_NORMAL, 1);