Cast to (const char *) in strrchr calls
[elinks.git] / src / session / task.c
blob8d01bc9c2ac562a6c80b220137ad9a0f40c6dfb2
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) {
59 /* ses->task.target.frame must be freed in the
60 * destroy_session() => abort_loading() =>
61 * abort_preloading() call chain. Normally,
62 * free_task() called from here would do that,
63 * but if the task has no type, free_task()
64 * cannot be called because an assertion would
65 * fail. There are several functions that set
66 * ses->task.target.frame without ses->task.type,
67 * so apparently it is allowed. */
68 mem_free_set(&ses->task.target.frame, NULL);
69 return;
72 cancel_download(&ses->loading, interrupt);
73 free_task(ses);
77 struct task {
78 struct session *ses;
79 struct uri *uri;
80 enum cache_mode cache_mode;
81 struct session_task session_task;
84 void
85 ses_load(struct session *ses, struct uri *uri, unsigned char *target_frame,
86 struct location *target_location, enum cache_mode cache_mode,
87 enum task_type task_type)
89 ses->loading.callback = (download_callback_T *) loading_callback;
90 ses->loading.data = ses;
91 ses->loading_uri = uri;
93 ses->task.type = task_type;
94 mem_free_set(&ses->task.target.frame, null_or_stracpy(target_frame));
95 ses->task.target.location = target_location;
97 load_uri(ses->loading_uri, ses->referrer, &ses->loading,
98 PRI_MAIN, cache_mode, -1);
101 static void
102 post_yes(void *task_)
104 struct task *task = task_;
106 abort_preloading(task->ses, 0);
108 /* XXX: Make the session inherit the URI. */
109 ses_load(task->ses, task->uri, task->session_task.target.frame,
110 task->session_task.target.location, task->cache_mode,
111 task->session_task.type);
114 static void
115 post_no(void *task_)
117 struct task *task = task_;
119 reload(task->ses, CACHE_MODE_NORMAL);
120 done_uri(task->uri);
123 /** Check if the URI is obfuscated (bug 382). The problem is said to occur when
124 * a URI designed to pass access a specific location with a supplied username,
125 * contains misleading chars prior to the @ symbol.
127 * An attacker can exploit this issue by supplying a malicious URI pointing to
128 * a page designed to mimic that of a trusted site, and tricking a victim who
129 * follows a link into believing they are actually at the trusted location.
131 * Only the user ID (and not also the password) is checked because only the
132 * user ID is displayed in the status bar. */
133 static int
134 check_malicious_uri(struct uri *uri)
136 unsigned char *user, *pos;
137 int warn = 0;
139 assert(uri->user && uri->userlen);
141 user = pos = memacpy(uri->user, uri->userlen);
142 if (!user) return 0;
144 decode_uri_for_display(user);
146 while (*pos) {
147 int length, trailing_dots;
149 for (length = 0; pos[length] != '\0'; length++)
150 if (!(isalnum(pos[length]) || pos[length] == '.'))
151 break;
153 /* Wind back so that the TLD part is checked correctly. */
154 for (trailing_dots = 0; trailing_dots < length; trailing_dots++)
155 if (!length || pos[length - trailing_dots - 1] != '.')
156 break;
158 /* Not perfect, but I am clueless as how to do better. Besides
159 * I don't really think it is an issue for ELinks. --jonas */
160 if (end_with_known_tld(pos, length - trailing_dots) != -1) {
161 warn = 1;
162 break;
165 pos += length;
167 while (*pos && (!isalnum(*pos) || *pos == '.'))
168 pos++;
171 mem_free(user);
173 return warn;
176 void
177 ses_goto(struct session *ses, struct uri *uri, unsigned char *target_frame,
178 struct location *target_location, enum cache_mode cache_mode,
179 enum task_type task_type, int redir)
181 /* [gettext_accelerator_context(ses_goto)] */
182 struct task *task;
183 int referrer_incomplete = 0;
184 int malicious_uri = 0;
185 int confirm_submit = uri->form && get_opt_bool("document.browse.forms"
186 ".confirm_submit", ses);
187 unsigned char *m1 = NULL, *message = NULL;
188 struct memory_list *mlist = NULL;
190 if (ses->doc_view
191 && ses->doc_view->document
192 && ses->doc_view->document->refresh) {
193 kill_document_refresh(ses->doc_view->document->refresh);
196 assertm(!ses->loading_uri, "Buggy URI reference counting");
198 /* Reset the redirect counter if this is not a redirect. */
199 if (!redir) {
200 ses->redirect_cnt = 0;
203 /* Figure out whether to confirm submit or not */
205 /* Only confirm submit if we are posting form data or a misleading URI
206 * was detected. */
207 /* Note uri->post might be empty here but we are still supposely
208 * posting form data so this should be more correct. */
210 if (uri->user && uri->userlen
211 && get_opt_bool("document.browse.links.warn_malicious", ses)
212 && check_malicious_uri(uri)) {
213 malicious_uri = 1;
214 confirm_submit = 1;
216 } else if (uri->form) {
217 /* First check if the referring URI was incomplete. It
218 * indicates that the posted form data might be incomplete too.
219 * See bug 460. */
220 if (ses->referrer) {
221 struct cache_entry *cached;
223 cached = find_in_cache(ses->referrer);
224 referrer_incomplete = (cached && cached->incomplete);
227 if (referrer_incomplete) {
228 confirm_submit = 1;
230 } else if (get_validated_cache_entry(uri, cache_mode)) {
231 confirm_submit = 0;
235 if (!confirm_submit) {
236 ses_load(ses, get_uri_reference(uri), target_frame,
237 target_location, cache_mode, task_type);
238 return;
241 task = mem_alloc(sizeof(*task));
242 if (!task) return;
244 task->ses = ses;
245 task->uri = get_uri_reference(uri);
246 task->cache_mode = cache_mode;
247 task->session_task.type = task_type;
248 task->session_task.target.frame = null_or_stracpy(target_frame);
249 task->session_task.target.location = target_location;
251 if (malicious_uri) {
252 unsigned char *host = memacpy(uri->host, uri->hostlen);
253 unsigned char *user = memacpy(uri->user, uri->userlen);
254 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
256 message = msg_text(ses->tab->term,
257 N_("The URL you are about to follow might be maliciously "
258 "crafted in order to confuse you. By following the URL "
259 "you will be connecting to host \"%s\" as user \"%s\".\n\n"
260 "Do you want to go to URL %s?"), host, user, uristring);
262 mem_free_if(host);
263 mem_free_if(user);
264 mem_free_if(uristring);
266 } else if (redir) {
267 m1 = N_("Do you want to follow the redirect and post form data "
268 "to URL %s?");
270 } else if (referrer_incomplete) {
271 m1 = N_("The form data you are about to post might be incomplete.\n"
272 "Do you want to post to URL %s?");
274 } else if (task_type == TASK_FORWARD) {
275 m1 = N_("Do you want to post form data to URL %s?");
277 } else {
278 m1 = N_("Do you want to repost form data to URL %s?");
281 if (!message && m1) {
282 unsigned char *uristring = get_uri_string(uri, URI_PUBLIC);
284 message = msg_text(ses->tab->term, m1, uristring);
285 mem_free_if(uristring);
288 add_to_ml(&mlist, task, (void *) NULL);
289 if (task->session_task.target.frame)
290 add_to_ml(&mlist, task->session_task.target.frame, (void *) NULL);
291 msg_box(ses->tab->term, mlist, MSGBOX_FREE_TEXT,
292 N_("Warning"), ALIGN_CENTER,
293 message,
294 task, 2,
295 MSG_BOX_BUTTON(N_("~Yes"), post_yes, B_ENTER),
296 MSG_BOX_BUTTON(N_("~No"), post_no, B_ESC));
300 /** If @a loaded_in_frame is set, this was called just to indicate a move inside
301 * a frameset, and we basically just reset the appropriate frame's view_state in
302 * that case. When clicking on a link inside a frame, the frame URI is somehow
303 * updated and added to the files-to-load queue, then ses_forward() is called
304 * with @a loaded_in_frame unset, duplicating the whole frameset's location,
305 * then later the file-to-load callback calls it for the particular frame with
306 * @a loaded_in_frame set. */
307 struct view_state *
308 ses_forward(struct session *ses, int loaded_in_frame)
310 struct location *loc = NULL;
311 struct view_state *vs;
313 if (!loaded_in_frame) {
314 free_files(ses);
315 mem_free_set(&ses->search_word, NULL);
319 if (!loaded_in_frame) {
320 loc = mem_calloc(1, sizeof(*loc));
321 if (!loc) return NULL;
322 copy_struct(&loc->download, &ses->loading);
325 if (ses->task.target.frame && *ses->task.target.frame) {
326 struct frame *frame;
328 assertm(have_location(ses), "no location yet");
329 if_assert_failed return NULL;
331 if (!loaded_in_frame) {
332 copy_location(loc, cur_loc(ses));
333 add_to_history(&ses->history, loc);
336 frame = ses_find_frame(ses, ses->task.target.frame);
337 if (!frame) {
338 if (!loaded_in_frame) {
339 del_from_history(&ses->history, loc);
340 destroy_location(loc);
342 mem_free_set(&ses->task.target.frame, NULL);
343 goto x;
346 vs = &frame->vs;
347 if (!loaded_in_frame) {
348 destroy_vs(vs, 1);
349 init_vs(vs, ses->loading_uri, vs->plain);
350 } else {
351 done_uri(vs->uri);
352 vs->uri = get_uri_reference(ses->loading_uri);
353 if (vs->doc_view) {
354 /* vs->doc_view itself will get detached in
355 * render_document_frames(), but that's too
356 * late for us. */
357 vs->doc_view->vs = NULL;
358 vs->doc_view = NULL;
360 #ifdef CONFIG_ECMASCRIPT
361 vs->ecmascript_fragile = 1;
362 #endif
365 } else {
366 assert(loc);
367 if_assert_failed return NULL;
369 init_list(loc->frames);
370 vs = &loc->vs;
371 init_vs(vs, ses->loading_uri, vs->plain);
372 add_to_history(&ses->history, loc);
375 ses->status.visited = 0;
377 /* This is another "branch" in the browsing, so throw away the current
378 * unhistory, we are venturing in another direction! */
379 if (ses->task.type == TASK_FORWARD)
380 clean_unhistory(&ses->history);
381 return vs;
384 static void
385 ses_imgmap(struct session *ses)
387 struct cache_entry *cached = find_in_cache(ses->loading_uri);
388 struct document_view *doc_view = current_frame(ses);
389 struct fragment *fragment;
390 struct memory_list *ml;
391 struct menu_item *menu;
393 if (!cached) {
394 INTERNAL("can't find cache entry");
395 return;
398 fragment = get_cache_fragment(cached);
399 if (!fragment) return;
401 if (!doc_view || !doc_view->document) return;
403 if (get_image_map(cached->head, fragment->data,
404 fragment->data + fragment->length,
405 &menu, &ml, ses->loading_uri,
406 &doc_view->document->options,
407 ses->task.target.frame,
408 get_terminal_codepage(ses->tab->term),
409 get_opt_codepage("document.codepage.assume", ses),
410 get_opt_bool("document.codepage.force_assumed", ses)))
411 return;
413 add_empty_window(ses->tab->term, (void (*)(void *)) freeml, ml);
414 do_menu(ses->tab->term, menu, ses, 0);
417 enum do_move {
418 DO_MOVE_ABORT,
419 DO_MOVE_DISPLAY,
420 DO_MOVE_DONE
423 static enum do_move
424 do_redirect(struct session *ses, struct download **download_p, struct cache_entry *cached)
426 enum task_type task = ses->task.type;
428 if (task == TASK_HISTORY && !have_location(ses))
429 return DO_MOVE_DISPLAY;
431 assertm(compare_uri(cached->uri, ses->loading_uri, URI_BASE),
432 "Redirecting using bad base URI");
434 if (cached->redirect->protocol == PROTOCOL_UNKNOWN)
435 return DO_MOVE_ABORT;
437 abort_loading(ses, 0);
438 if (have_location(ses))
439 *download_p = &cur_loc(ses)->download;
440 else
441 *download_p = NULL;
443 set_session_referrer(ses, cached->uri);
445 switch (task) {
446 case TASK_NONE:
447 break;
448 case TASK_FORWARD:
450 protocol_external_handler_T *fn;
451 struct uri *uri = cached->redirect;
453 fn = get_protocol_external_handler(ses->tab->term, uri);
454 if (fn) {
455 fn(ses, uri);
456 *download_p = NULL;
457 return DO_MOVE_ABORT;
460 /* Fall through. */
461 case TASK_IMGMAP:
462 ses_goto(ses, cached->redirect, ses->task.target.frame, NULL,
463 CACHE_MODE_NORMAL, task, 1);
464 return DO_MOVE_DONE;
465 case TASK_HISTORY:
466 ses_goto(ses, cached->redirect, NULL, ses->task.target.location,
467 CACHE_MODE_NORMAL, TASK_RELOAD, 1);
468 return DO_MOVE_DONE;
469 case TASK_RELOAD:
470 ses_goto(ses, cached->redirect, NULL, NULL,
471 ses->reloadlevel, TASK_RELOAD, 1);
472 return DO_MOVE_DONE;
475 return DO_MOVE_DISPLAY;
478 static enum do_move
479 do_move(struct session *ses, struct download **download_p)
481 struct cache_entry *cached;
483 assert(download_p && *download_p);
484 assertm(ses->loading_uri != NULL, "no ses->loading_uri");
485 if_assert_failed return DO_MOVE_ABORT;
487 if (ses->loading_uri->protocol == PROTOCOL_UNKNOWN)
488 return DO_MOVE_ABORT;
490 /* Handling image map needs to scan the source of the loaded document
491 * so all of it has to be available. */
492 if (ses->task.type == TASK_IMGMAP && is_in_progress_state((*download_p)->state))
493 return DO_MOVE_ABORT;
495 cached = (*download_p)->cached;
496 if (!cached) return DO_MOVE_ABORT;
498 if (cached->redirect && ses->redirect_cnt++ < MAX_REDIRECTS) {
499 enum do_move d = do_redirect(ses, download_p, cached);
501 if (d != DO_MOVE_DISPLAY) return d;
504 kill_timer(&ses->display_timer);
506 switch (ses->task.type) {
507 case TASK_NONE:
508 break;
509 case TASK_FORWARD:
510 if (setup_download_handler(ses, &ses->loading, cached, 0)) {
511 free_task(ses);
512 reload(ses, CACHE_MODE_NORMAL);
513 return DO_MOVE_DONE;
515 break;
516 case TASK_IMGMAP:
517 ses_imgmap(ses);
518 break;
519 case TASK_HISTORY:
520 ses_history_move(ses);
521 break;
522 case TASK_RELOAD:
523 ses->task.target.location = cur_loc(ses)->prev;
524 ses_history_move(ses);
525 ses_forward(ses, 0);
526 break;
529 if (is_in_progress_state((*download_p)->state)) {
530 if (have_location(ses))
531 *download_p = &cur_loc(ses)->download;
532 move_download(&ses->loading, *download_p, PRI_MAIN);
533 } else if (have_location(ses)) {
534 cur_loc(ses)->download.state = ses->loading.state;
537 free_task(ses);
538 return DO_MOVE_DISPLAY;
541 static void
542 loading_callback(struct download *download, struct session *ses)
544 enum do_move d;
546 assertm(ses->task.type, "loading_callback: no ses->task");
547 if_assert_failed return;
549 d = do_move(ses, &download);
550 if (!download) return;
551 if (d == DO_MOVE_DONE) goto end;
553 if (d == DO_MOVE_DISPLAY) {
554 download->callback = (download_callback_T *) doc_loading_callback;
555 download->data = ses;
556 display_timer(ses);
559 if (is_in_result_state(download->state)) {
560 if (ses->task.type) free_task(ses);
561 if (d == DO_MOVE_DISPLAY) doc_loading_callback(download, ses);
564 if (is_in_result_state(download->state)
565 && !is_in_state(download->state, S_OK)) {
566 print_error_dialog(ses, download->state,
567 download->conn ? download->conn->uri : NULL,
568 download->pri);
569 if (d == DO_MOVE_ABORT) reload(ses, CACHE_MODE_NORMAL);
572 end:
573 check_questions_queue(ses);
574 print_screen_status(ses);
578 static void
579 do_follow_url(struct session *ses, struct uri *uri, unsigned char *target,
580 enum task_type task, enum cache_mode cache_mode, int do_referrer)
582 struct uri *referrer = NULL;
583 protocol_external_handler_T *external_handler;
585 if (!uri) {
586 print_error_dialog(ses, connection_state(S_BAD_URL), uri, PRI_CANCEL);
587 return;
590 external_handler = get_protocol_external_handler(ses->tab->term, uri);
591 if (external_handler) {
592 external_handler(ses, uri);
593 return;
596 if (do_referrer) {
597 struct document_view *doc_view = current_frame(ses);
599 if (doc_view && doc_view->document)
600 referrer = doc_view->document->uri;
603 if (target && !strcmp(target, "_blank")) {
604 int mode = get_opt_int("document.browse.links.target_blank",
605 ses);
607 if (mode == 3
608 && !get_cmd_opt_bool("anonymous")
609 && can_open_in_new(ses->tab->term)
610 && !get_cmd_opt_bool("no-connect")
611 && !get_cmd_opt_bool("no-home")) {
612 enum term_env_type env = ses->tab->term->environment;
614 open_uri_in_new_window(ses, uri, referrer, env,
615 cache_mode, task);
616 return;
619 if (mode > 0) {
620 struct session *new_ses;
622 new_ses = init_session(ses, ses->tab->term, uri, (mode == 2));
623 if (new_ses) ses = new_ses;
627 ses->reloadlevel = cache_mode;
629 if (ses->task.type == task) {
630 if (compare_uri(ses->loading_uri, uri, 0)) {
631 /* We're already loading the URL. */
632 return;
636 abort_loading(ses, 0);
638 set_session_referrer(ses, referrer);
640 ses_goto(ses, uri, target, NULL, cache_mode, task, 0);
643 static void
644 follow_url(struct session *ses, struct uri *uri, unsigned char *target,
645 enum task_type task, enum cache_mode cache_mode, int referrer)
647 #ifdef CONFIG_SCRIPTING
648 static int follow_url_event_id = EVENT_NONE;
649 unsigned char *uristring;
651 uristring = uri && !uri->post ? get_uri_string(uri, URI_BASE | URI_FRAGMENT)
652 : NULL;
654 /* Do nothing if we do not have a URI or if it is a POST request
655 * because scripts can corrupt POST requests leading to bad
656 * things happening later on. */
657 if (!uristring) {
658 do_follow_url(ses, uri, target, task, cache_mode, referrer);
659 return;
662 set_event_id(follow_url_event_id, "follow-url");
663 trigger_event(follow_url_event_id, &uristring, ses);
665 if (!uristring || !*uristring) {
666 mem_free_if(uristring);
667 return;
670 /* FIXME: Compare if uristring and struri(uri) are equal */
671 /* FIXME: When uri->post will no longer be an encoded string (but
672 * hopefully some refcounted object) we will have to assign the post
673 * data object to the translated URI. */
674 uri = get_translated_uri(uristring, ses->tab->term->cwd);
675 mem_free(uristring);
676 #endif
678 do_follow_url(ses, uri, target, task, cache_mode, referrer);
680 #ifdef CONFIG_SCRIPTING
681 if (uri) done_uri(uri);
682 #endif
685 void
686 goto_uri(struct session *ses, struct uri *uri)
688 follow_url(ses, uri, NULL, TASK_FORWARD, CACHE_MODE_NORMAL, 0);
691 void
692 goto_uri_frame(struct session *ses, struct uri *uri,
693 unsigned char *target, enum cache_mode cache_mode)
695 follow_url(ses, uri, target, TASK_FORWARD, cache_mode, 1);
698 void
699 delayed_goto_uri_frame(void *data)
701 struct delayed_open *deo = data;
702 struct frame *frame;
704 assert(deo);
705 frame = deo->target ? ses_find_frame(deo->ses, deo->target) : NULL;
706 if (frame)
707 goto_uri_frame(deo->ses, deo->uri, frame->name, CACHE_MODE_NORMAL);
708 else {
709 goto_uri_frame(deo->ses, deo->uri, NULL, CACHE_MODE_NORMAL);
711 done_uri(deo->uri);
712 mem_free_if(deo->target);
713 mem_free(deo);
716 /* menu_func_T */
717 void
718 map_selected(struct terminal *term, void *ld_, void *ses_)
720 struct link_def *ld = ld_;
721 struct session *ses = ses_;
722 struct uri *uri = get_uri(ld->link, 0);
724 goto_uri_frame(ses, uri, ld->target, CACHE_MODE_NORMAL);
725 if (uri) done_uri(uri);
729 void
730 goto_url(struct session *ses, unsigned char *url)
732 struct uri *uri = get_uri(url, 0);
734 goto_uri(ses, uri);
735 if (uri) done_uri(uri);
738 struct uri *
739 get_hooked_uri(unsigned char *uristring, struct session *ses, unsigned char *cwd)
741 struct uri *uri;
743 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
744 static int goto_url_event_id = EVENT_NONE;
746 uristring = stracpy(uristring);
747 if (!uristring) return NULL;
749 set_event_id(goto_url_event_id, "goto-url");
750 trigger_event(goto_url_event_id, &uristring, ses);
751 if (!uristring) return NULL;
752 #endif
754 uri = *uristring ? get_translated_uri(uristring, cwd) : NULL;
756 #if defined(CONFIG_SCRIPTING) || defined(CONFIG_URI_REWRITE)
757 mem_free(uristring);
758 #endif
759 return uri;
762 void
763 goto_url_with_hook(struct session *ses, unsigned char *url)
765 unsigned char *cwd = ses->tab->term->cwd;
766 struct uri *uri;
768 /* Bail out if passed empty string from goto-url dialog */
769 if (!*url) return;
771 uri = get_hooked_uri(url, ses, cwd);
772 if (!uri) return;
774 goto_uri(ses, uri);
776 done_uri(uri);
780 goto_url_home(struct session *ses)
782 unsigned char *homepage = get_opt_str("ui.sessions.homepage", ses);
784 if (!*homepage) homepage = getenv("WWW_HOME");
785 if (!homepage || !*homepage) homepage = WWW_HOME_URL;
787 if (!homepage || !*homepage) return 0;
789 goto_url_with_hook(ses, homepage);
790 return 1;
793 /* TODO: Should there be goto_imgmap_reload() ? */
795 void
796 goto_imgmap(struct session *ses, struct uri *uri, unsigned char *target)
798 follow_url(ses, uri, target, TASK_IMGMAP, CACHE_MODE_NORMAL, 1);