762: Instead of setting a bare pointer for task.target.frame always
[elinks.git] / src / session / download.c
blob00cb83bcd2a3ddc4f13b0b3ce3c45452b7e6a35e
1 /** Downloads managment
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <errno.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #ifdef HAVE_SYS_CYGWIN_H
13 #include <sys/cygwin.h>
14 #endif
15 #include <sys/types.h>
16 #ifdef HAVE_FCNTL_H
17 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
18 #endif
19 #include <sys/stat.h>
20 #ifdef HAVE_UNISTD_H
21 #include <unistd.h>
22 #endif
23 #include <utime.h>
25 #include "elinks.h"
27 #include "bfu/dialog.h"
28 #include "cache/cache.h"
29 #include "config/options.h"
30 #include "dialogs/document.h"
31 #include "dialogs/download.h"
32 #include "dialogs/menu.h"
33 #include "intl/gettext/libintl.h"
34 #include "main/object.h"
35 #include "mime/mime.h"
36 #include "network/connection.h"
37 #include "network/progress.h"
38 #include "network/state.h"
39 #include "osdep/osdep.h"
40 #include "protocol/bittorrent/dialogs.h"
41 #include "protocol/date.h"
42 #include "protocol/protocol.h"
43 #include "protocol/uri.h"
44 #include "session/download.h"
45 #include "session/history.h"
46 #include "session/location.h"
47 #include "session/session.h"
48 #include "session/task.h"
49 #include "terminal/draw.h"
50 #include "terminal/screen.h"
51 #include "terminal/terminal.h"
52 #include "util/conv.h"
53 #include "util/error.h"
54 #include "util/file.h"
55 #include "util/lists.h"
56 #include "util/memlist.h"
57 #include "util/memory.h"
58 #include "util/string.h"
59 #include "util/time.h"
62 /* TODO: tp_*() should be in separate file, I guess? --pasky */
65 INIT_LIST_OF(struct file_download, downloads);
67 int
68 download_is_progressing(struct download *download)
70 return download
71 && is_in_state(download->state, S_TRANS)
72 && has_progress(download->progress);
75 int
76 are_there_downloads(void)
78 struct file_download *file_download;
80 foreach (file_download, downloads)
81 if (!file_download->external_handler)
82 return 1;
84 return 0;
88 static void download_data(struct download *download, struct file_download *file_download);
90 struct file_download *
91 init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd)
93 struct file_download *file_download;
95 file_download = mem_calloc(1, sizeof(*file_download));
96 if (!file_download) return NULL;
98 /* Actually we could allow fragments in the URI and just change all the
99 * places that compares and shows the URI, but for now it is much
100 * easier this way. */
101 file_download->uri = get_composed_uri(uri, URI_BASE);
102 if (!file_download->uri) {
103 mem_free(file_download);
104 return NULL;
107 init_download_display(file_download);
109 file_download->file = file;
110 file_download->handle = fd;
112 file_download->download.callback = (download_callback_T *) download_data;
113 file_download->download.data = file_download;
114 file_download->ses = ses;
115 /* The tab may be closed, but we will still want to ie. open the
116 * handler on that terminal. */
117 file_download->term = ses->tab->term;
119 object_nolock(file_download, "file_download"); /* Debugging purpose. */
120 add_to_list(downloads, file_download);
122 return file_download;
126 void
127 abort_download(struct file_download *file_download)
129 #if 0
130 /* When hacking to cleanup the download code, remove lots of duplicated
131 * code and implement stuff from bug 435 we should reintroduce this
132 * assertion. Currently it will trigger often and shows that the
133 * download dialog code potentially could access free()d memory. */
134 assert(!is_object_used(file_download));
135 #endif
136 done_download_display(file_download);
138 if (file_download->ses)
139 check_questions_queue(file_download->ses);
141 if (file_download->dlg_data)
142 cancel_dialog(file_download->dlg_data, NULL);
143 cancel_download(&file_download->download, file_download->stop);
144 if (file_download->uri) done_uri(file_download->uri);
146 if (file_download->handle != -1) {
147 prealloc_truncate(file_download->handle, file_download->seek);
148 close(file_download->handle);
151 mem_free_if(file_download->external_handler);
152 if (file_download->file) {
153 if (file_download->delete) unlink(file_download->file);
154 mem_free(file_download->file);
156 del_from_list(file_download);
157 mem_free(file_download);
161 static void
162 kill_downloads_to_file(unsigned char *file)
164 struct file_download *file_download;
166 foreach (file_download, downloads) {
167 if (strcmp(file_download->file, file))
168 continue;
170 file_download = file_download->prev;
171 abort_download(file_download->next);
176 void
177 abort_all_downloads(void)
179 while (!list_empty(downloads))
180 abort_download(downloads.next);
184 void
185 destroy_downloads(struct session *ses)
187 struct file_download *file_download, *next;
188 struct session *s;
190 /* We are supposed to blat all downloads to external handlers belonging
191 * to @ses, but we will refuse to do so if there is another session
192 * bound to this terminal. That looks like the reasonable thing to do,
193 * fulfilling the principle of least astonishment. */
194 foreach (s, sessions) {
195 if (s == ses || s->tab->term != ses->tab->term)
196 continue;
198 foreach (file_download, downloads) {
199 if (file_download->ses != ses)
200 continue;
202 file_download->ses = s;
205 return;
208 foreachsafe (file_download, next, downloads) {
209 if (file_download->ses != ses)
210 continue;
212 if (!file_download->external_handler) {
213 file_download->ses = NULL;
214 continue;
217 abort_download(file_download);
221 void
222 detach_downloads_from_terminal(struct terminal *term)
224 struct file_download *file_download, *next;
226 assert(term != NULL);
227 if_assert_failed return;
229 foreachsafe (file_download, next, downloads) {
230 if (file_download->term != term)
231 continue;
233 if (!file_download->external_handler) {
234 file_download->term = NULL;
235 if (file_download->ses
236 && file_download->ses->tab->term == term)
237 file_download->ses = NULL;
238 continue;
241 abort_download(file_download);
245 static void
246 download_error_dialog(struct file_download *file_download, int saved_errno)
248 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
249 struct session *ses = file_download->ses;
250 struct terminal *term = file_download->term;
252 if (!ses) return;
254 info_box(term, MSGBOX_FREE_TEXT,
255 N_("Download error"), ALIGN_CENTER,
256 msg_text(term, N_("Could not create file '%s':\n%s"),
257 file_download->file, emsg));
260 static int
261 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
263 struct fragment *frag;
265 if (file_download->download.progress && file_download->download.progress->seek) {
266 file_download->seek = file_download->download.progress->seek;
267 file_download->download.progress->seek = 0;
268 /* This is exclusive with the prealloc, thus we can perform
269 * this in front of that thing safely. */
270 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
271 download_error_dialog(file_download, errno);
272 return 0;
276 foreach (frag, cached->frag) {
277 off_t remain = file_download->seek - frag->offset;
278 int *h = &file_download->handle;
279 ssize_t w;
281 if (remain < 0 || frag->length <= remain)
282 continue;
284 #ifdef USE_OPEN_PREALLOC
285 if (!file_download->seek
286 && (!file_download->download.progress
287 || file_download->download.progress->size > 0)) {
288 close(*h);
289 *h = open_prealloc(file_download->file,
290 O_CREAT|O_WRONLY|O_TRUNC,
291 0666,
292 file_download->download.progress
293 ? file_download->download.progress->size
294 : cached->length);
295 if (*h == -1) {
296 download_error_dialog(file_download, errno);
297 return 0;
299 set_bin(*h);
301 #endif
303 w = safe_write(*h, frag->data + remain, frag->length - remain);
304 if (w == -1) {
305 download_error_dialog(file_download, errno);
306 return 0;
309 file_download->seek += w;
312 return 1;
315 static void
316 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
318 if (term && get_opt_int("document.download.notify_bell",
319 file_download->ses)
320 + file_download->notify >= 2) {
321 beep_terminal(term);
324 abort_download(file_download);
327 static void
328 download_data_store(struct download *download, struct file_download *file_download)
330 struct terminal *term = file_download->term;
332 assert_terminal_ptr_not_dangling(term);
333 if_assert_failed term = file_download->term = NULL;
335 if (is_in_progress_state(download->state)) {
336 if (file_download->dlg_data)
337 redraw_dialog(file_download->dlg_data, 1);
338 return;
341 /* If the original terminal of the download has been closed,
342 * display any messages in the default terminal instead. */
343 if (term == NULL)
344 term = get_default_terminal(); /* may be NULL too */
346 if (!is_in_state(download->state, S_OK)) {
347 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
348 struct connection_state state = download->state;
350 /* abort_download_and_beep allows term==NULL. */
351 abort_download_and_beep(file_download, term);
353 if (!url) return;
355 if (term) {
356 info_box(term, MSGBOX_FREE_TEXT,
357 N_("Download error"), ALIGN_CENTER,
358 msg_text(term, N_("Error downloading %s:\n\n%s"),
359 url, get_state_message(state, term)));
361 mem_free(url);
362 return;
365 if (file_download->external_handler) {
366 if (term == NULL) {
367 /* There is no terminal in which to run the handler.
368 * Abort the download. file_download->delete should
369 * be 1 here so that the following call also deletes
370 * the temporary file. */
371 abort_download(file_download);
372 return;
375 prealloc_truncate(file_download->handle, file_download->seek);
376 close(file_download->handle);
377 file_download->handle = -1;
378 exec_on_terminal(term, file_download->external_handler,
379 file_download->file,
380 file_download->block ? TERM_EXEC_FG : TERM_EXEC_BG);
381 file_download->delete = 0;
382 abort_download_and_beep(file_download, term);
383 return;
386 if (file_download->notify && term) {
387 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
389 /* This is apparently a little racy. Deleting the box item will
390 * update the download browser _after_ the notification dialog
391 * has been drawn whereby it will be hidden. This should make
392 * the download browser update before launcing any
393 * notification. */
394 done_download_display(file_download);
396 if (url) {
397 info_box(term, MSGBOX_FREE_TEXT,
398 N_("Download"), ALIGN_CENTER,
399 msg_text(term, N_("Download complete:\n%s"), url));
400 mem_free(url);
404 if (file_download->remotetime
405 && get_opt_bool("document.download.set_original_time",
406 file_download->ses)) {
407 struct utimbuf foo;
409 foo.actime = foo.modtime = file_download->remotetime;
410 utime(file_download->file, &foo);
413 /* abort_download_and_beep allows term==NULL. */
414 abort_download_and_beep(file_download, term);
417 static void
418 download_data(struct download *download, struct file_download *file_download)
420 struct cache_entry *cached = download->cached;
422 if (!cached || is_in_queued_state(download->state)) {
423 download_data_store(download, file_download);
424 return;
427 if (cached->last_modified)
428 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
430 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
431 cancel_download(&file_download->download, 0);
433 assertm(compare_uri(cached->uri, file_download->uri, 0),
434 "Redirecting using bad base URI");
436 done_uri(file_download->uri);
438 file_download->uri = get_uri_reference(cached->redirect);
439 file_download->download.state = connection_state(S_WAIT_REDIR);
441 if (file_download->dlg_data)
442 redraw_dialog(file_download->dlg_data, 1);
444 load_uri(file_download->uri, cached->uri, &file_download->download,
445 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
446 download->progress ? download->progress->start : 0);
448 return;
451 if (!write_cache_entry_to_file(cached, file_download)) {
452 detach_connection(download, file_download->seek);
453 abort_download(file_download);
454 return;
457 detach_connection(download, file_download->seek);
458 download_data_store(download, file_download);
462 /* XXX: We assume that resume is everytime zero in lun's callbacks. */
463 struct lun_hop {
464 struct terminal *term;
465 unsigned char *ofile, *file;
467 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T);
468 void *data;
471 enum {
472 COMMON_DOWNLOAD_DO = 0,
473 CONTINUE_DOWNLOAD_DO
476 struct cmdw_hop {
477 int magic; /* Must be first --witekfl */
478 struct session *ses;
479 unsigned char *real_file;
482 struct codw_hop {
483 int magic; /* must be first --witekfl */
484 struct type_query *type_query;
485 unsigned char *real_file;
486 unsigned char *file;
489 struct cdf_hop {
490 unsigned char **real_file;
491 void (*callback)(struct terminal *, int, void *, download_flags_T);
492 void *data;
495 static void
496 lun_alternate(void *lun_hop_)
498 struct lun_hop *lun_hop = lun_hop_;
500 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, DOWNLOAD_START);
501 mem_free_if(lun_hop->ofile);
502 mem_free(lun_hop);
505 static void
506 lun_cancel(void *lun_hop_)
508 struct lun_hop *lun_hop = lun_hop_;
510 lun_hop->callback(lun_hop->term, NULL, lun_hop->data, DOWNLOAD_START);
511 mem_free_if(lun_hop->ofile);
512 mem_free_if(lun_hop->file);
513 mem_free(lun_hop);
516 static void
517 lun_overwrite(void *lun_hop_)
519 struct lun_hop *lun_hop = lun_hop_;
521 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0);
522 mem_free_if(lun_hop->file);
523 mem_free(lun_hop);
526 static void common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags);
528 static void
529 lun_resume(void *lun_hop_)
531 struct lun_hop *lun_hop = lun_hop_;
532 struct cdf_hop *cdf_hop = lun_hop->data;
534 int magic = *(int *)cdf_hop->data;
536 if (magic == CONTINUE_DOWNLOAD_DO) {
537 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
539 if (!cmdw_hop) {
540 lun_cancel(lun_hop);
541 return;
542 } else {
543 struct codw_hop *codw_hop = cdf_hop->data;
544 struct type_query *type_query = codw_hop->type_query;
546 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
547 cmdw_hop->ses = type_query->ses;
548 /* FIXME: Current ses->download_uri is overwritten here --witekfl */
549 cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri);
551 if (type_query->external_handler) mem_free_if(codw_hop->file);
552 tp_cancel(type_query);
553 mem_free(codw_hop);
555 cdf_hop->real_file = &cmdw_hop->real_file;
556 cdf_hop->data = cmdw_hop;
557 cdf_hop->callback = common_download_do;
560 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, DOWNLOAD_RESUME);
561 mem_free_if(lun_hop->file);
562 mem_free(lun_hop);
566 static void
567 lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T flags,
568 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T flags),
569 void *data)
571 /* [gettext_accelerator_context(.lookup_unique_name)] */
572 struct lun_hop *lun_hop;
573 unsigned char *file;
574 int overwrite;
576 ofile = expand_tilde(ofile);
578 /* Minor code duplication to prevent useless call to get_opt_int()
579 * if possible. --Zas */
580 if (flags & DOWNLOAD_RESUME) {
581 callback(term, ofile, data, flags);
582 return;
585 /* !overwrite means always silently overwrite, which may be admitelly
586 * indeed a little confusing ;-) */
587 overwrite = get_opt_int("document.download.overwrite", NULL);
588 if (!overwrite) {
589 /* Nothing special to do... */
590 callback(term, ofile, data, flags);
591 return;
594 /* Check if file is a directory, and use a default name if it's the
595 * case. */
596 if (file_is_dir(ofile)) {
597 info_box(term, MSGBOX_FREE_TEXT,
598 N_("Download error"), ALIGN_CENTER,
599 msg_text(term, N_("'%s' is a directory."),
600 ofile));
601 mem_free(ofile);
602 callback(term, NULL, data, flags);
603 return;
606 /* Check if the file already exists (file != ofile). */
607 file = get_unique_name(ofile);
609 if (!file || overwrite == 1 || file == ofile) {
610 /* Still nothing special to do... */
611 if (file != ofile) mem_free(ofile);
612 callback(term, file, data, flags);
613 return;
616 /* overwrite == 2 (ask) and file != ofile (=> original file already
617 * exists) */
619 lun_hop = mem_calloc(1, sizeof(*lun_hop));
620 if (!lun_hop) {
621 if (file != ofile) mem_free(file);
622 mem_free(ofile);
623 callback(term, NULL, data, flags);
624 return;
626 lun_hop->term = term;
627 lun_hop->ofile = ofile;
628 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
629 lun_hop->callback = callback;
630 lun_hop->data = data;
632 msg_box(term, NULL, MSGBOX_FREE_TEXT,
633 N_("File exists"), ALIGN_CENTER,
634 msg_text(term, N_("This file already exists:\n"
635 "%s\n\n"
636 "The alternative filename is:\n"
637 "%s"),
638 empty_string_or_(lun_hop->ofile),
639 empty_string_or_(file)),
640 lun_hop, 4,
641 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
642 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
643 MSG_BOX_BUTTON(N_("~Resume download of the original file"), lun_resume, 0),
644 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
649 static void
650 create_download_file_do(struct terminal *term, unsigned char *file, void *data,
651 download_flags_T flags)
653 struct cdf_hop *cdf_hop = data;
654 unsigned char *wd;
655 int h = -1;
656 int saved_errno;
657 #ifdef NO_FILE_SECURITY
658 int sf = 0;
659 #else
660 int sf = flags & DOWNLOAD_EXTERNAL;
661 #endif
663 if (!file) goto finish;
665 wd = get_cwd();
666 set_cwd(term->cwd);
668 /* Create parent directories if needed. */
669 mkalldirs(file);
671 /* O_APPEND means repositioning at the end of file before each write(),
672 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
673 * --pasky */
674 h = open(file, O_CREAT | O_WRONLY | (flags & DOWNLOAD_RESUME ? 0 : O_TRUNC)
675 | (sf && !(flags & DOWNLOAD_RESUME) ? O_EXCL : 0),
676 sf ? 0600 : 0666);
677 saved_errno = errno; /* Saved in case of ... --Zas */
679 if (wd) {
680 set_cwd(wd);
681 mem_free(wd);
684 if (h == -1) {
685 info_box(term, MSGBOX_FREE_TEXT,
686 N_("Download error"), ALIGN_CENTER,
687 msg_text(term, N_("Could not create file '%s':\n%s"),
688 file, strerror(saved_errno)));
690 mem_free(file);
691 goto finish;
693 } else {
694 set_bin(h);
696 if (!(flags & DOWNLOAD_EXTERNAL)) {
697 unsigned char *download_dir = get_opt_str("document.download.directory", NULL);
698 int i;
700 safe_strncpy(download_dir, file, MAX_STR_LEN);
702 /* Find the used directory so it's available in history */
703 for (i = strlen(download_dir); i >= 0; i--)
704 if (dir_sep(download_dir[i]))
705 break;
706 download_dir[i + 1] = 0;
710 if (cdf_hop->real_file)
711 *cdf_hop->real_file = file;
712 else
713 mem_free(file);
715 finish:
716 cdf_hop->callback(term, h, cdf_hop->data, flags);
717 mem_free(cdf_hop);
720 void
721 create_download_file(struct terminal *term, unsigned char *fi,
722 unsigned char **real_file, download_flags_T flags,
723 void (*callback)(struct terminal *, int, void *, download_flags_T),
724 void *data)
726 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
727 unsigned char *wd;
729 if (!cdf_hop) {
730 callback(term, -1, data, 0);
731 return;
734 cdf_hop->real_file = real_file;
735 cdf_hop->callback = callback;
736 cdf_hop->data = data;
738 /* FIXME: The wd bussiness is probably useless here? --pasky */
739 wd = get_cwd();
740 set_cwd(term->cwd);
742 /* Also the tilde will be expanded here. */
743 lookup_unique_name(term, fi, flags, create_download_file_do, cdf_hop);
745 if (wd) {
746 set_cwd(wd);
747 mem_free(wd);
752 static unsigned char *
753 get_temp_name(struct uri *uri)
755 struct string name;
756 unsigned char *extension;
757 /* FIXME
758 * We use tempnam() here, which is unsafe (race condition), for now.
759 * This should be changed at some time, but it needs an in-depth work
760 * of whole download code. --Zas */
761 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
763 if (!nm) return NULL;
765 if (!init_string(&name)) {
766 free(nm);
767 return NULL;
770 add_to_string(&name, nm);
771 free(nm);
773 extension = get_extension_from_uri(uri);
774 if (extension) {
775 add_shell_safe_to_string(&name, extension, strlen(extension));
776 mem_free(extension);
779 return name.source;
783 static unsigned char *
784 subst_file(unsigned char *prog, unsigned char *file)
786 struct string name;
787 /* When there is no %s in the mailcap entry, the handler program reads
788 * data from stdin instead of a file. */
789 int input = 1;
791 if (!init_string(&name)) return NULL;
793 while (*prog) {
794 int p;
796 for (p = 0; prog[p] && prog[p] != '%'; p++);
798 add_bytes_to_string(&name, prog, p);
799 prog += p;
801 if (*prog == '%') {
802 input = 0;
803 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
804 #ifdef MAX_PATH
805 unsigned char new_path[MAX_PATH];
806 #else
807 unsigned char new_path[1024];
808 #endif
810 cygwin_conv_to_full_win32_path(file, new_path);
811 add_to_string(&name, new_path);
812 #else
813 add_shell_quoted_to_string(&name, file, strlen(file));
814 #endif
815 prog++;
819 if (input) {
820 struct string s;
822 if (init_string(&s)) {
823 add_to_string(&s, "/bin/cat ");
824 add_shell_quoted_to_string(&s, file, strlen(file));
825 add_to_string(&s, " | ");
826 add_string_to_string(&s, &name);
827 done_string(&name);
828 return s.source;
831 return name.source;
836 static void
837 common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
839 struct file_download *file_download;
840 struct cmdw_hop *cmdw_hop = data;
841 unsigned char *file = cmdw_hop->real_file;
842 struct session *ses = cmdw_hop->ses;
843 struct stat buf;
845 mem_free(cmdw_hop);
847 if (!file || fstat(fd, &buf)) return;
849 file_download = init_file_download(ses->download_uri, ses, file, fd);
850 if (!file_download) return;
852 if (flags & DOWNLOAD_RESUME) file_download->seek = buf.st_size;
854 display_download(ses->tab->term, file_download, ses);
856 load_uri(file_download->uri, ses->referrer, &file_download->download,
857 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
860 static void
861 common_download(struct session *ses, unsigned char *file, download_flags_T flags)
863 struct cmdw_hop *cmdw_hop;
865 if (!ses->download_uri) return;
867 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
868 if (!cmdw_hop) return;
869 cmdw_hop->ses = ses;
870 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
872 kill_downloads_to_file(file);
874 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, flags,
875 common_download_do, cmdw_hop);
878 void
879 start_download(void *ses, unsigned char *file)
881 common_download(ses, file, DOWNLOAD_START);
884 void
885 resume_download(void *ses, unsigned char *file)
887 common_download(ses, file, DOWNLOAD_RESUME);
892 static void
893 continue_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
895 struct codw_hop *codw_hop = data;
896 struct file_download *file_download = NULL;
897 struct type_query *type_query;
899 assert(codw_hop);
900 assert(codw_hop->type_query);
901 assert(codw_hop->type_query->uri);
902 assert(codw_hop->type_query->ses);
904 type_query = codw_hop->type_query;
905 if (!codw_hop->real_file) goto cancel;
907 file_download = init_file_download(type_query->uri, type_query->ses,
908 codw_hop->real_file, fd);
909 if (!file_download) goto cancel;
911 if (type_query->external_handler) {
912 file_download->external_handler = subst_file(type_query->external_handler,
913 codw_hop->file);
914 file_download->delete = 1;
915 mem_free(codw_hop->file);
916 mem_free_set(&type_query->external_handler, NULL);
919 file_download->block = !!type_query->block;
921 /* Done here and not in init_file_download() so that the external
922 * handler can become initialized. */
923 display_download(term, file_download, type_query->ses);
925 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
926 done_type_query(type_query);
928 mem_free(codw_hop);
929 return;
931 cancel:
932 if (type_query->external_handler) mem_free_if(codw_hop->file);
933 tp_cancel(type_query);
934 mem_free(codw_hop);
937 static void
938 continue_download(void *data, unsigned char *file)
940 struct type_query *type_query = data;
941 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
943 if (!codw_hop) {
944 tp_cancel(type_query);
945 return;
948 if (type_query->external_handler) {
949 /* FIXME: get_temp_name() calls tempnam(). --Zas */
950 file = get_temp_name(type_query->uri);
951 if (!file) {
952 mem_free(codw_hop);
953 tp_cancel(type_query);
954 return;
958 codw_hop->type_query = type_query;
959 codw_hop->file = file;
960 codw_hop->magic = CONTINUE_DOWNLOAD_DO;
962 kill_downloads_to_file(file);
964 create_download_file(type_query->ses->tab->term, file,
965 &codw_hop->real_file,
966 type_query->external_handler
967 ? DOWNLOAD_START | DOWNLOAD_EXTERNAL
968 : DOWNLOAD_START,
969 continue_download_do, codw_hop);
973 static struct type_query *
974 find_type_query(struct session *ses)
976 struct type_query *type_query;
978 foreach (type_query, ses->type_queries)
979 if (compare_uri(type_query->uri, ses->loading_uri, 0))
980 return type_query;
982 return NULL;
985 static struct type_query *
986 init_type_query(struct session *ses, struct download *download,
987 struct cache_entry *cached)
989 struct type_query *type_query;
991 type_query = mem_calloc(1, sizeof(*type_query));
992 if (!type_query) return NULL;
994 type_query->uri = get_uri_reference(ses->loading_uri);
995 type_query->ses = ses;
996 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
998 type_query->cached = cached;
999 type_query->cgi = cached->cgi;
1000 object_lock(type_query->cached);
1002 move_download(download, &type_query->download, PRI_MAIN);
1003 download->state = connection_state(S_OK);
1005 add_to_list(ses->type_queries, type_query);
1007 return type_query;
1010 void
1011 done_type_query(struct type_query *type_query)
1013 /* Unregister any active download */
1014 cancel_download(&type_query->download, 0);
1016 object_unlock(type_query->cached);
1017 done_uri(type_query->uri);
1018 mem_free_if(type_query->external_handler);
1019 mem_free_if(type_query->target_frame);
1020 del_from_list(type_query);
1021 mem_free(type_query);
1025 void
1026 tp_cancel(void *data)
1028 struct type_query *type_query = data;
1030 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
1031 cancel_download(&type_query->download, 1);
1032 done_type_query(type_query);
1036 void
1037 tp_save(struct type_query *type_query)
1039 mem_free_set(&type_query->external_handler, NULL);
1040 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1043 /** This button handler uses the add_dlg_button() interface so that pressing
1044 * 'Show header' will not close the type query dialog. */
1045 static widget_handler_status_T
1046 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1048 struct type_query *type_query = widget_data->widget->data;
1050 cached_header_dialog(type_query->ses, type_query->cached);
1052 return EVENT_PROCESSED;
1056 /** @bug FIXME: We need to modify this function to take frame data
1057 * instead, as we want to use this function for frames as well (now,
1058 * when frame has content type text/plain, it is ignored and displayed
1059 * as HTML). */
1060 void
1061 tp_display(struct type_query *type_query)
1063 struct view_state *vs;
1064 struct session *ses = type_query->ses;
1065 struct uri *loading_uri = ses->loading_uri;
1066 unsigned char *target_frame = null_or_stracpy(ses->task.target.frame);
1068 ses->loading_uri = type_query->uri;
1069 mem_free_set(&ses->task.target.frame, null_or_stracpy(type_query->target_frame));
1070 vs = ses_forward(ses, /* type_query->frame */ 0);
1071 if (vs) vs->plain = 1;
1072 ses->loading_uri = loading_uri;
1073 mem_free_set(&ses->task.target.frame, target_frame);
1075 if (/* !type_query->frame */ 1) {
1076 struct download *old = &type_query->download;
1077 struct download *new = &cur_loc(ses)->download;
1079 new->callback = (download_callback_T *) doc_loading_callback;
1080 new->data = ses;
1082 move_download(old, new, PRI_MAIN);
1085 display_timer(ses);
1086 done_type_query(type_query);
1089 static void
1090 tp_open(struct type_query *type_query)
1092 if (!type_query->external_handler || !*type_query->external_handler) {
1093 tp_display(type_query);
1094 return;
1097 if (type_query->uri->protocol == PROTOCOL_FILE && !type_query->cgi) {
1098 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1099 unsigned char *handler = NULL;
1101 if (file) {
1102 decode_uri(file);
1103 handler = subst_file(type_query->external_handler, file);
1104 mem_free(file);
1107 if (handler) {
1108 exec_on_terminal(type_query->ses->tab->term,
1109 handler, "",
1110 type_query->block ? TERM_EXEC_FG : TERM_EXEC_BG);
1111 mem_free(handler);
1114 done_type_query(type_query);
1115 return;
1118 continue_download(type_query, "");
1122 static void
1123 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
1125 /* [gettext_accelerator_context(.do_type_query)] */
1126 struct string filename;
1127 unsigned char *description;
1128 unsigned char *desc_sep;
1129 unsigned char *format, *text, *title;
1130 struct dialog *dlg;
1131 #define TYPE_QUERY_WIDGETS_COUNT 8
1132 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1133 struct terminal *term = type_query->ses->tab->term;
1134 struct memory_list *ml;
1135 struct dialog_data *dlg_data;
1136 int selected_widget;
1138 mem_free_set(&type_query->external_handler, NULL);
1140 if (handler) {
1141 type_query->block = handler->block;
1142 if (!handler->ask) {
1143 type_query->external_handler = stracpy(handler->program);
1144 tp_open(type_query);
1145 return;
1148 /* Start preparing for the type query dialog. */
1149 description = handler->description;
1150 desc_sep = *description ? "; " : "";
1151 title = N_("What to do?");
1153 } else {
1154 title = N_("Unknown type");
1155 description = "";
1156 desc_sep = "";
1159 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1160 if (!dlg) return;
1162 if (init_string(&filename)) {
1163 add_mime_filename_to_string(&filename, type_query->uri);
1165 /* Let's make the filename pretty for display & save */
1166 /* TODO: The filename can be the empty string here. See bug 396. */
1167 #ifdef CONFIG_UTF8
1168 if (term->utf8_cp)
1169 decode_uri_string(&filename);
1170 else
1171 #endif /* CONFIG_UTF8 */
1172 decode_uri_string_for_display(&filename);
1175 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1176 /* For "default directory index pages" with wrong content-type
1177 * the filename can be NULL, e.g. http://www.spamhaus.org in bug 396. */
1178 if (filename.length) {
1179 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1180 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1181 } else {
1182 format = _("What would you like to do with the file (type: %s%s%s)?", term);
1183 snprintf(text, MAX_STR_LEN, format, ct, desc_sep, description);
1186 done_string(&filename);
1188 dlg->title = _(title, term);
1189 dlg->layouter = generic_dialog_layouter;
1190 dlg->layout.padding_top = 1;
1191 dlg->layout.fit_datalen = 1;
1192 dlg->udata2 = type_query;
1194 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1196 /* Add input field or text widget with info about the program handler. */
1197 if (!get_cmd_opt_bool("anonymous")) {
1198 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1200 if (!field) {
1201 mem_free(dlg);
1202 return;
1205 if (handler && handler->program) {
1206 safe_strncpy(field, handler->program, MAX_STR_LEN);
1209 /* xgettext:no-c-format */
1210 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1211 0, 0, NULL, MAX_STR_LEN, field, NULL);
1212 type_query->external_handler = field;
1214 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1215 selected_widget = 3;
1217 } else if (handler) {
1218 unsigned char *field = text + MAX_STR_LEN;
1220 format = _("The file will be opened with the program '%s'.", term);
1221 snprintf(field, MAX_STR_LEN, format, handler->program);
1222 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1224 type_query->external_handler = stracpy(handler->program);
1225 if (!type_query->external_handler) {
1226 mem_free(dlg);
1227 return;
1230 widgets--;
1231 selected_widget = 2;
1233 } else {
1234 widgets -= 2;
1235 selected_widget = 1;
1238 /* Add buttons if they are both usable and allowed. */
1240 if (!get_cmd_opt_bool("anonymous") || handler) {
1241 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1242 (done_handler_T *) tp_open, type_query);
1243 } else {
1244 widgets--;
1247 if (!get_cmd_opt_bool("anonymous")) {
1248 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1249 (done_handler_T *) tp_save, type_query);
1250 } else {
1251 widgets--;
1254 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1255 (done_handler_T *) tp_display, type_query);
1257 if (type_query->cached && type_query->cached->head) {
1258 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1259 tp_show_header, type_query);
1260 } else {
1261 widgets--;
1264 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1265 (done_handler_T *) tp_cancel, type_query);
1267 add_dlg_end(dlg, widgets);
1269 ml = getml(dlg, (void *) NULL);
1270 if (!ml) {
1271 /* XXX: Assume that the allocated @external_handler will be
1272 * freed when releasing the @type_query. */
1273 mem_free(dlg);
1274 return;
1277 dlg_data = do_dialog(term, dlg, ml);
1278 /* Don't focus the text field; we want the user to be able
1279 * to select a button by typing the first letter of its label
1280 * without having to first leave the text field. */
1281 if (dlg_data) {
1282 select_widget_by_id(dlg_data, selected_widget);
1286 struct {
1287 unsigned char *type;
1288 unsigned int plain:1;
1289 } static const known_types[] = {
1290 { "text/html", 0 },
1291 { "text/plain", 1 },
1292 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1293 #if CONFIG_DOM
1294 { "application/docbook+xml", 1 },
1295 { "application/rss+xml", 0 },
1296 { "application/xbel+xml", 1 },
1297 { "application/xbel", 1 },
1298 { "application/x-xbel", 1 },
1299 #endif
1300 { NULL, 1 },
1304 setup_download_handler(struct session *ses, struct download *loading,
1305 struct cache_entry *cached, int frame)
1307 struct mime_handler *handler;
1308 struct view_state *vs;
1309 struct type_query *type_query;
1310 unsigned char *ctype = get_content_type(cached);
1311 int plaintext = 1;
1312 int ret = 0;
1313 int xwin, i;
1315 if (!ctype || !*ctype)
1316 goto plaintext_follow;
1318 for (i = 0; known_types[i].type; i++) {
1319 if (c_strcasecmp(ctype, known_types[i].type))
1320 continue;
1322 plaintext = known_types[i].plain;
1323 goto plaintext_follow;
1326 xwin = ses->tab->term->environment & ENV_XWIN;
1327 handler = get_mime_type_handler(ctype, xwin);
1329 if (!handler && strlen(ctype) >= 4 && !c_strncasecmp(ctype, "text", 4))
1330 goto plaintext_follow;
1332 type_query = find_type_query(ses);
1333 if (type_query) {
1334 ret = 1;
1335 } else {
1336 type_query = init_type_query(ses, loading, cached);
1337 if (type_query) {
1338 ret = 1;
1339 #ifdef CONFIG_BITTORRENT
1340 /* A terrible waste of a good MIME handler here, but we want
1341 * to use the type_query this is easier. */
1342 if ((!c_strcasecmp(ctype, "application/x-bittorrent")
1343 || !c_strcasecmp(ctype, "application/x-torrent"))
1344 && !get_cmd_opt_bool("anonymous"))
1345 query_bittorrent_dialog(type_query);
1346 else
1347 #endif
1348 do_type_query(type_query, ctype, handler);
1352 mem_free_if(handler);
1354 return ret;
1356 plaintext_follow:
1357 vs = ses_forward(ses, frame);
1358 if (vs) vs->plain = plaintext;
1359 return 0;