big dialogs: set_curosr2 -> set_dlg_cursor.
[elinks.git] / src / session / download.c
blob08e488ee9a0b2218b798e7092a3aefc0ee49d695
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);
222 static void
223 download_error_dialog(struct file_download *file_download, int saved_errno)
225 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
226 struct session *ses = file_download->ses;
227 struct terminal *term = file_download->term;
229 if (!ses) return;
231 info_box(term, MSGBOX_FREE_TEXT,
232 N_("Download error"), ALIGN_CENTER,
233 msg_text(term, N_("Could not create file '%s':\n%s"),
234 file_download->file, emsg));
237 static int
238 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
240 struct fragment *frag;
242 if (file_download->download.progress && file_download->download.progress->seek) {
243 file_download->seek = file_download->download.progress->seek;
244 file_download->download.progress->seek = 0;
245 /* This is exclusive with the prealloc, thus we can perform
246 * this in front of that thing safely. */
247 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
248 download_error_dialog(file_download, errno);
249 return 0;
253 foreach (frag, cached->frag) {
254 off_t remain = file_download->seek - frag->offset;
255 int *h = &file_download->handle;
256 ssize_t w;
258 if (remain < 0 || frag->length <= remain)
259 continue;
261 #ifdef USE_OPEN_PREALLOC
262 if (!file_download->seek
263 && (!file_download->download.progress
264 || file_download->download.progress->size > 0)) {
265 close(*h);
266 *h = open_prealloc(file_download->file,
267 O_CREAT|O_WRONLY|O_TRUNC,
268 0666,
269 file_download->download.progress
270 ? file_download->download.progress->size
271 : cached->length);
272 if (*h == -1) {
273 download_error_dialog(file_download, errno);
274 return 0;
276 set_bin(*h);
278 #endif
280 w = safe_write(*h, frag->data + remain, frag->length - remain);
281 if (w == -1) {
282 download_error_dialog(file_download, errno);
283 return 0;
286 file_download->seek += w;
289 return 1;
292 static void
293 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
295 if (term && get_opt_int("document.download.notify_bell",
296 file_download->ses)
297 + file_download->notify >= 2) {
298 beep_terminal(term);
301 abort_download(file_download);
304 static void
305 download_data_store(struct download *download, struct file_download *file_download)
307 struct terminal *term = file_download->term;
309 if (!term) {
310 /* No term here, so no beep. --Zas */
311 abort_download(file_download);
312 return;
315 if (is_in_progress_state(download->state)) {
316 if (file_download->dlg_data)
317 redraw_dialog(file_download->dlg_data, 1);
318 return;
321 if (!is_in_state(download->state, S_OK)) {
322 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
323 struct connection_state state = download->state;
325 abort_download_and_beep(file_download, term);
327 if (!url) return;
329 info_box(term, MSGBOX_FREE_TEXT,
330 N_("Download error"), ALIGN_CENTER,
331 msg_text(term, N_("Error downloading %s:\n\n%s"),
332 url, get_state_message(state, term)));
333 mem_free(url);
334 return;
337 if (file_download->external_handler) {
338 prealloc_truncate(file_download->handle, file_download->seek);
339 close(file_download->handle);
340 file_download->handle = -1;
341 exec_on_terminal(term, file_download->external_handler,
342 file_download->file,
343 file_download->block ? TERM_EXEC_FG : TERM_EXEC_BG);
344 file_download->delete = 0;
345 abort_download_and_beep(file_download, term);
346 return;
349 if (file_download->notify) {
350 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
352 /* This is apparently a little racy. Deleting the box item will
353 * update the download browser _after_ the notification dialog
354 * has been drawn whereby it will be hidden. This should make
355 * the download browser update before launcing any
356 * notification. */
357 done_download_display(file_download);
359 if (url) {
360 info_box(term, MSGBOX_FREE_TEXT,
361 N_("Download"), ALIGN_CENTER,
362 msg_text(term, N_("Download complete:\n%s"), url));
363 mem_free(url);
367 if (file_download->remotetime
368 && get_opt_bool("document.download.set_original_time",
369 file_download->ses)) {
370 struct utimbuf foo;
372 foo.actime = foo.modtime = file_download->remotetime;
373 utime(file_download->file, &foo);
376 abort_download_and_beep(file_download, term);
379 static void
380 download_data(struct download *download, struct file_download *file_download)
382 struct cache_entry *cached = download->cached;
384 if (!cached || is_in_queued_state(download->state)) {
385 download_data_store(download, file_download);
386 return;
389 if (cached->last_modified)
390 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
392 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
393 cancel_download(&file_download->download, 0);
395 assertm(compare_uri(cached->uri, file_download->uri, 0),
396 "Redirecting using bad base URI");
398 done_uri(file_download->uri);
400 file_download->uri = get_uri_reference(cached->redirect);
401 file_download->download.state = connection_state(S_WAIT_REDIR);
403 if (file_download->dlg_data)
404 redraw_dialog(file_download->dlg_data, 1);
406 load_uri(file_download->uri, cached->uri, &file_download->download,
407 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
408 download->progress ? download->progress->start : 0);
410 return;
413 if (!write_cache_entry_to_file(cached, file_download)) {
414 detach_connection(download, file_download->seek);
415 abort_download(file_download);
416 return;
419 detach_connection(download, file_download->seek);
420 download_data_store(download, file_download);
424 /* XXX: We assume that resume is everytime zero in lun's callbacks. */
425 struct lun_hop {
426 struct terminal *term;
427 unsigned char *ofile, *file;
429 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T);
430 void *data;
433 enum {
434 COMMON_DOWNLOAD_DO = 0,
435 CONTINUE_DOWNLOAD_DO
438 struct cmdw_hop {
439 int magic; /* Must be first --witekfl */
440 struct session *ses;
441 unsigned char *real_file;
444 struct codw_hop {
445 int magic; /* must be first --witekfl */
446 struct type_query *type_query;
447 unsigned char *real_file;
448 unsigned char *file;
451 struct cdf_hop {
452 unsigned char **real_file;
453 void (*callback)(struct terminal *, int, void *, download_flags_T);
454 void *data;
457 static void
458 lun_alternate(void *lun_hop_)
460 struct lun_hop *lun_hop = lun_hop_;
462 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, DOWNLOAD_START);
463 mem_free_if(lun_hop->ofile);
464 mem_free(lun_hop);
467 static void
468 lun_cancel(void *lun_hop_)
470 struct lun_hop *lun_hop = lun_hop_;
472 lun_hop->callback(lun_hop->term, NULL, lun_hop->data, DOWNLOAD_START);
473 mem_free_if(lun_hop->ofile);
474 mem_free_if(lun_hop->file);
475 mem_free(lun_hop);
478 static void
479 lun_overwrite(void *lun_hop_)
481 struct lun_hop *lun_hop = lun_hop_;
483 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0);
484 mem_free_if(lun_hop->file);
485 mem_free(lun_hop);
488 static void common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags);
490 static void
491 lun_resume(void *lun_hop_)
493 struct lun_hop *lun_hop = lun_hop_;
494 struct cdf_hop *cdf_hop = lun_hop->data;
496 int magic = *(int *)cdf_hop->data;
498 if (magic == CONTINUE_DOWNLOAD_DO) {
499 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
501 if (!cmdw_hop) {
502 lun_cancel(lun_hop);
503 return;
504 } else {
505 struct codw_hop *codw_hop = cdf_hop->data;
506 struct type_query *type_query = codw_hop->type_query;
508 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
509 cmdw_hop->ses = type_query->ses;
510 /* FIXME: Current ses->download_uri is overwritten here --witekfl */
511 cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri);
513 if (type_query->external_handler) mem_free_if(codw_hop->file);
514 tp_cancel(type_query);
515 mem_free(codw_hop);
517 cdf_hop->real_file = &cmdw_hop->real_file;
518 cdf_hop->data = cmdw_hop;
519 cdf_hop->callback = common_download_do;
522 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, DOWNLOAD_RESUME);
523 mem_free_if(lun_hop->file);
524 mem_free(lun_hop);
528 static void
529 lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T flags,
530 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T flags),
531 void *data)
533 /* [gettext_accelerator_context(.lookup_unique_name)] */
534 struct lun_hop *lun_hop;
535 unsigned char *file;
536 int overwrite;
538 ofile = expand_tilde(ofile);
540 /* Minor code duplication to prevent useless call to get_opt_int()
541 * if possible. --Zas */
542 if (flags & DOWNLOAD_RESUME) {
543 callback(term, ofile, data, flags);
544 return;
547 /* !overwrite means always silently overwrite, which may be admitelly
548 * indeed a little confusing ;-) */
549 overwrite = get_opt_int("document.download.overwrite", NULL);
550 if (!overwrite) {
551 /* Nothing special to do... */
552 callback(term, ofile, data, flags);
553 return;
556 /* Check if file is a directory, and use a default name if it's the
557 * case. */
558 if (file_is_dir(ofile)) {
559 info_box(term, MSGBOX_FREE_TEXT,
560 N_("Download error"), ALIGN_CENTER,
561 msg_text(term, N_("'%s' is a directory."),
562 ofile));
563 mem_free(ofile);
564 callback(term, NULL, data, flags);
565 return;
568 /* Check if the file already exists (file != ofile). */
569 file = get_unique_name(ofile);
571 if (!file || overwrite == 1 || file == ofile) {
572 /* Still nothing special to do... */
573 if (file != ofile) mem_free(ofile);
574 callback(term, file, data, flags);
575 return;
578 /* overwrite == 2 (ask) and file != ofile (=> original file already
579 * exists) */
581 lun_hop = mem_calloc(1, sizeof(*lun_hop));
582 if (!lun_hop) {
583 if (file != ofile) mem_free(file);
584 mem_free(ofile);
585 callback(term, NULL, data, flags);
586 return;
588 lun_hop->term = term;
589 lun_hop->ofile = ofile;
590 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
591 lun_hop->callback = callback;
592 lun_hop->data = data;
594 msg_box(term, NULL, MSGBOX_FREE_TEXT,
595 N_("File exists"), ALIGN_CENTER,
596 msg_text(term, N_("This file already exists:\n"
597 "%s\n\n"
598 "The alternative filename is:\n"
599 "%s"),
600 empty_string_or_(lun_hop->ofile),
601 empty_string_or_(file)),
602 lun_hop, 4,
603 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
604 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
605 MSG_BOX_BUTTON(N_("~Resume download of the original file"), lun_resume, 0),
606 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
611 static void
612 create_download_file_do(struct terminal *term, unsigned char *file, void *data,
613 download_flags_T flags)
615 struct cdf_hop *cdf_hop = data;
616 unsigned char *wd;
617 int h = -1;
618 int saved_errno;
619 #ifdef NO_FILE_SECURITY
620 int sf = 0;
621 #else
622 int sf = flags & DOWNLOAD_EXTERNAL;
623 #endif
625 if (!file) goto finish;
627 wd = get_cwd();
628 set_cwd(term->cwd);
630 /* Create parent directories if needed. */
631 mkalldirs(file);
633 /* O_APPEND means repositioning at the end of file before each write(),
634 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
635 * --pasky */
636 h = open(file, O_CREAT | O_WRONLY | (flags & DOWNLOAD_RESUME ? 0 : O_TRUNC)
637 | (sf && !(flags & DOWNLOAD_RESUME) ? O_EXCL : 0),
638 sf ? 0600 : 0666);
639 saved_errno = errno; /* Saved in case of ... --Zas */
641 if (wd) {
642 set_cwd(wd);
643 mem_free(wd);
646 if (h == -1) {
647 info_box(term, MSGBOX_FREE_TEXT,
648 N_("Download error"), ALIGN_CENTER,
649 msg_text(term, N_("Could not create file '%s':\n%s"),
650 file, strerror(saved_errno)));
652 mem_free(file);
653 goto finish;
655 } else {
656 set_bin(h);
658 if (!(flags & DOWNLOAD_EXTERNAL)) {
659 unsigned char *download_dir = get_opt_str("document.download.directory", NULL);
660 int i;
662 safe_strncpy(download_dir, file, MAX_STR_LEN);
664 /* Find the used directory so it's available in history */
665 for (i = strlen(download_dir); i >= 0; i--)
666 if (dir_sep(download_dir[i]))
667 break;
668 download_dir[i + 1] = 0;
672 if (cdf_hop->real_file)
673 *cdf_hop->real_file = file;
674 else
675 mem_free(file);
677 finish:
678 cdf_hop->callback(term, h, cdf_hop->data, flags);
679 mem_free(cdf_hop);
682 void
683 create_download_file(struct terminal *term, unsigned char *fi,
684 unsigned char **real_file, download_flags_T flags,
685 void (*callback)(struct terminal *, int, void *, download_flags_T),
686 void *data)
688 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
689 unsigned char *wd;
691 if (!cdf_hop) {
692 callback(term, -1, data, 0);
693 return;
696 cdf_hop->real_file = real_file;
697 cdf_hop->callback = callback;
698 cdf_hop->data = data;
700 /* FIXME: The wd bussiness is probably useless here? --pasky */
701 wd = get_cwd();
702 set_cwd(term->cwd);
704 /* Also the tilde will be expanded here. */
705 lookup_unique_name(term, fi, flags, create_download_file_do, cdf_hop);
707 if (wd) {
708 set_cwd(wd);
709 mem_free(wd);
714 static unsigned char *
715 get_temp_name(struct uri *uri)
717 struct string name;
718 unsigned char *extension;
719 /* FIXME
720 * We use tempnam() here, which is unsafe (race condition), for now.
721 * This should be changed at some time, but it needs an in-depth work
722 * of whole download code. --Zas */
723 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
725 if (!nm) return NULL;
727 if (!init_string(&name)) {
728 free(nm);
729 return NULL;
732 add_to_string(&name, nm);
733 free(nm);
735 extension = get_extension_from_uri(uri);
736 if (extension) {
737 add_shell_safe_to_string(&name, extension, strlen(extension));
738 mem_free(extension);
741 return name.source;
745 static unsigned char *
746 subst_file(unsigned char *prog, unsigned char *file)
748 struct string name;
749 /* When there is no %s in the mailcap entry, the handler program reads
750 * data from stdin instead of a file. */
751 int input = 1;
753 if (!init_string(&name)) return NULL;
755 while (*prog) {
756 int p;
758 for (p = 0; prog[p] && prog[p] != '%'; p++);
760 add_bytes_to_string(&name, prog, p);
761 prog += p;
763 if (*prog == '%') {
764 input = 0;
765 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
766 #ifdef MAX_PATH
767 unsigned char new_path[MAX_PATH];
768 #else
769 unsigned char new_path[1024];
770 #endif
772 cygwin_conv_to_full_win32_path(file, new_path);
773 add_to_string(&name, new_path);
774 #else
775 add_shell_quoted_to_string(&name, file, strlen(file));
776 #endif
777 prog++;
781 if (input) {
782 struct string s;
784 if (init_string(&s)) {
785 add_to_string(&s, "/bin/cat ");
786 add_shell_quoted_to_string(&s, file, strlen(file));
787 add_to_string(&s, " | ");
788 add_string_to_string(&s, &name);
789 done_string(&name);
790 return s.source;
793 return name.source;
798 static void
799 common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
801 struct file_download *file_download;
802 struct cmdw_hop *cmdw_hop = data;
803 unsigned char *file = cmdw_hop->real_file;
804 struct session *ses = cmdw_hop->ses;
805 struct stat buf;
807 mem_free(cmdw_hop);
809 if (!file || fstat(fd, &buf)) return;
811 file_download = init_file_download(ses->download_uri, ses, file, fd);
812 if (!file_download) return;
814 if (flags & DOWNLOAD_RESUME) file_download->seek = buf.st_size;
816 display_download(ses->tab->term, file_download, ses);
818 load_uri(file_download->uri, ses->referrer, &file_download->download,
819 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
822 static void
823 common_download(struct session *ses, unsigned char *file, download_flags_T flags)
825 struct cmdw_hop *cmdw_hop;
827 if (!ses->download_uri) return;
829 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
830 if (!cmdw_hop) return;
831 cmdw_hop->ses = ses;
832 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
834 kill_downloads_to_file(file);
836 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, flags,
837 common_download_do, cmdw_hop);
840 void
841 start_download(void *ses, unsigned char *file)
843 common_download(ses, file, DOWNLOAD_START);
846 void
847 resume_download(void *ses, unsigned char *file)
849 common_download(ses, file, DOWNLOAD_RESUME);
854 static void
855 continue_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
857 struct codw_hop *codw_hop = data;
858 struct file_download *file_download = NULL;
859 struct type_query *type_query;
861 assert(codw_hop);
862 assert(codw_hop->type_query);
863 assert(codw_hop->type_query->uri);
864 assert(codw_hop->type_query->ses);
866 type_query = codw_hop->type_query;
867 if (!codw_hop->real_file) goto cancel;
869 file_download = init_file_download(type_query->uri, type_query->ses,
870 codw_hop->real_file, fd);
871 if (!file_download) goto cancel;
873 if (type_query->external_handler) {
874 file_download->external_handler = subst_file(type_query->external_handler,
875 codw_hop->file);
876 file_download->delete = 1;
877 mem_free(codw_hop->file);
878 mem_free_set(&type_query->external_handler, NULL);
881 file_download->block = !!type_query->block;
883 /* Done here and not in init_file_download() so that the external
884 * handler can become initialized. */
885 display_download(term, file_download, type_query->ses);
887 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
888 done_type_query(type_query);
890 mem_free(codw_hop);
891 return;
893 cancel:
894 if (type_query->external_handler) mem_free_if(codw_hop->file);
895 tp_cancel(type_query);
896 mem_free(codw_hop);
899 static void
900 continue_download(void *data, unsigned char *file)
902 struct type_query *type_query = data;
903 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
905 if (!codw_hop) {
906 tp_cancel(type_query);
907 return;
910 if (type_query->external_handler) {
911 /* FIXME: get_temp_name() calls tempnam(). --Zas */
912 file = get_temp_name(type_query->uri);
913 if (!file) {
914 mem_free(codw_hop);
915 tp_cancel(type_query);
916 return;
920 codw_hop->type_query = type_query;
921 codw_hop->file = file;
922 codw_hop->magic = CONTINUE_DOWNLOAD_DO;
924 kill_downloads_to_file(file);
926 create_download_file(type_query->ses->tab->term, file,
927 &codw_hop->real_file,
928 type_query->external_handler
929 ? DOWNLOAD_START | DOWNLOAD_EXTERNAL
930 : DOWNLOAD_START,
931 continue_download_do, codw_hop);
935 static struct type_query *
936 find_type_query(struct session *ses)
938 struct type_query *type_query;
940 foreach (type_query, ses->type_queries)
941 if (compare_uri(type_query->uri, ses->loading_uri, 0))
942 return type_query;
944 return NULL;
947 static struct type_query *
948 init_type_query(struct session *ses, struct download *download,
949 struct cache_entry *cached)
951 struct type_query *type_query;
953 type_query = mem_calloc(1, sizeof(*type_query));
954 if (!type_query) return NULL;
956 type_query->uri = get_uri_reference(ses->loading_uri);
957 type_query->ses = ses;
958 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
960 type_query->cached = cached;
961 type_query->cgi = cached->cgi;
962 object_lock(type_query->cached);
964 move_download(download, &type_query->download, PRI_MAIN);
965 download->state = connection_state(S_OK);
967 add_to_list(ses->type_queries, type_query);
969 return type_query;
972 void
973 done_type_query(struct type_query *type_query)
975 /* Unregister any active download */
976 cancel_download(&type_query->download, 0);
978 object_unlock(type_query->cached);
979 done_uri(type_query->uri);
980 mem_free_if(type_query->external_handler);
981 mem_free_if(type_query->target_frame);
982 del_from_list(type_query);
983 mem_free(type_query);
987 void
988 tp_cancel(void *data)
990 struct type_query *type_query = data;
992 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
993 cancel_download(&type_query->download, 1);
994 done_type_query(type_query);
998 void
999 tp_save(struct type_query *type_query)
1001 mem_free_set(&type_query->external_handler, NULL);
1002 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1005 /** This button handler uses the add_dlg_button() interface so that pressing
1006 * 'Show header' will not close the type query dialog. */
1007 static widget_handler_status_T
1008 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1010 struct type_query *type_query = widget_data->widget->data;
1012 cached_header_dialog(type_query->ses, type_query->cached);
1014 return EVENT_PROCESSED;
1018 /** @bug FIXME: We need to modify this function to take frame data
1019 * instead, as we want to use this function for frames as well (now,
1020 * when frame has content type text/plain, it is ignored and displayed
1021 * as HTML). */
1022 void
1023 tp_display(struct type_query *type_query)
1025 struct view_state *vs;
1026 struct session *ses = type_query->ses;
1027 struct uri *loading_uri = ses->loading_uri;
1028 unsigned char *target_frame = ses->task.target.frame;
1030 ses->loading_uri = type_query->uri;
1031 ses->task.target.frame = type_query->target_frame;
1032 vs = ses_forward(ses, /* type_query->frame */ 0);
1033 if (vs) vs->plain = 1;
1034 ses->loading_uri = loading_uri;
1035 ses->task.target.frame = target_frame;
1037 if (/* !type_query->frame */ 1) {
1038 struct download *old = &type_query->download;
1039 struct download *new = &cur_loc(ses)->download;
1041 new->callback = (download_callback_T *) doc_loading_callback;
1042 new->data = ses;
1044 move_download(old, new, PRI_MAIN);
1047 display_timer(ses);
1048 done_type_query(type_query);
1051 static void
1052 tp_open(struct type_query *type_query)
1054 if (!type_query->external_handler || !*type_query->external_handler) {
1055 tp_display(type_query);
1056 return;
1059 if (type_query->uri->protocol == PROTOCOL_FILE && !type_query->cgi) {
1060 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1061 unsigned char *handler = NULL;
1063 if (file) {
1064 decode_uri(file);
1065 handler = subst_file(type_query->external_handler, file);
1066 mem_free(file);
1069 if (handler) {
1070 exec_on_terminal(type_query->ses->tab->term,
1071 handler, "",
1072 type_query->block ? TERM_EXEC_FG : TERM_EXEC_BG);
1073 mem_free(handler);
1076 done_type_query(type_query);
1077 return;
1080 continue_download(type_query, "");
1084 static void
1085 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
1087 /* [gettext_accelerator_context(.do_type_query)] */
1088 struct string filename;
1089 unsigned char *description;
1090 unsigned char *desc_sep;
1091 unsigned char *format, *text, *title;
1092 struct dialog *dlg;
1093 #define TYPE_QUERY_WIDGETS_COUNT 8
1094 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1095 struct terminal *term = type_query->ses->tab->term;
1096 struct memory_list *ml;
1097 struct dialog_data *dlg_data;
1098 int selected_widget;
1100 mem_free_set(&type_query->external_handler, NULL);
1102 if (handler) {
1103 type_query->block = handler->block;
1104 if (!handler->ask) {
1105 type_query->external_handler = stracpy(handler->program);
1106 tp_open(type_query);
1107 return;
1110 /* Start preparing for the type query dialog. */
1111 description = handler->description;
1112 desc_sep = *description ? "; " : "";
1113 title = N_("What to do?");
1115 } else {
1116 title = N_("Unknown type");
1117 description = "";
1118 desc_sep = "";
1121 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1122 if (!dlg) return;
1124 if (init_string(&filename)) {
1125 add_mime_filename_to_string(&filename, type_query->uri);
1127 /* Let's make the filename pretty for display & save */
1128 /* TODO: The filename can be the empty string here. See bug 396. */
1129 #ifdef CONFIG_UTF8
1130 if (term->utf8_cp)
1131 decode_uri_string(&filename);
1132 else
1133 #endif /* CONFIG_UTF8 */
1134 decode_uri_string_for_display(&filename);
1137 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1138 /* For "default directory index pages" with wrong content-type
1139 * the filename can be NULL, e.g. http://www.spamhaus.org in bug 396. */
1140 if (filename.length) {
1141 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1142 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1143 } else {
1144 format = _("What would you like to do with the file (type: %s%s%s)?", term);
1145 snprintf(text, MAX_STR_LEN, format, ct, desc_sep, description);
1148 done_string(&filename);
1150 dlg->title = _(title, term);
1151 dlg->layouter = generic_dialog_layouter;
1152 dlg->layout.padding_top = 1;
1153 dlg->layout.fit_datalen = 1;
1154 dlg->udata2 = type_query;
1156 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1158 /* Add input field or text widget with info about the program handler. */
1159 if (!get_cmd_opt_bool("anonymous")) {
1160 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1162 if (!field) {
1163 mem_free(dlg);
1164 return;
1167 if (handler && handler->program) {
1168 safe_strncpy(field, handler->program, MAX_STR_LEN);
1171 /* xgettext:no-c-format */
1172 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1173 0, 0, NULL, MAX_STR_LEN, field, NULL);
1174 type_query->external_handler = field;
1176 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1177 selected_widget = 3;
1179 } else if (handler) {
1180 unsigned char *field = text + MAX_STR_LEN;
1182 format = _("The file will be opened with the program '%s'.", term);
1183 snprintf(field, MAX_STR_LEN, format, handler->program);
1184 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1186 type_query->external_handler = stracpy(handler->program);
1187 if (!type_query->external_handler) {
1188 mem_free(dlg);
1189 return;
1192 widgets--;
1193 selected_widget = 2;
1195 } else {
1196 widgets -= 2;
1197 selected_widget = 1;
1200 /* Add buttons if they are both usable and allowed. */
1202 if (!get_cmd_opt_bool("anonymous") || handler) {
1203 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1204 (done_handler_T *) tp_open, type_query);
1205 } else {
1206 widgets--;
1209 if (!get_cmd_opt_bool("anonymous")) {
1210 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1211 (done_handler_T *) tp_save, type_query);
1212 } else {
1213 widgets--;
1216 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1217 (done_handler_T *) tp_display, type_query);
1219 if (type_query->cached && type_query->cached->head) {
1220 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1221 tp_show_header, type_query);
1222 } else {
1223 widgets--;
1226 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1227 (done_handler_T *) tp_cancel, type_query);
1229 add_dlg_end(dlg, widgets);
1231 ml = getml(dlg, (void *) NULL);
1232 if (!ml) {
1233 /* XXX: Assume that the allocated @external_handler will be
1234 * freed when releasing the @type_query. */
1235 mem_free(dlg);
1236 return;
1239 dlg_data = do_dialog(term, dlg, ml);
1240 /* Don't focus the text field; we want the user to be able
1241 * to select a button by typing the first letter of its label
1242 * without having to first leave the text field. */
1243 if (dlg_data) {
1244 select_widget_by_id(dlg_data, selected_widget);
1248 struct {
1249 unsigned char *type;
1250 unsigned int plain:1;
1251 } static const known_types[] = {
1252 { "text/html", 0 },
1253 { "text/plain", 1 },
1254 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1255 #if CONFIG_DOM
1256 { "application/docbook+xml", 1 },
1257 { "application/rss+xml", 0 },
1258 { "application/xbel+xml", 1 },
1259 { "application/xbel", 1 },
1260 { "application/x-xbel", 1 },
1261 #endif
1262 { NULL, 1 },
1266 setup_download_handler(struct session *ses, struct download *loading,
1267 struct cache_entry *cached, int frame)
1269 struct mime_handler *handler;
1270 struct view_state *vs;
1271 struct type_query *type_query;
1272 unsigned char *ctype = get_content_type(cached);
1273 int plaintext = 1;
1274 int ret = 0;
1275 int xwin, i;
1277 if (!ctype || !*ctype)
1278 goto plaintext_follow;
1280 for (i = 0; known_types[i].type; i++) {
1281 if (strcasecmp(ctype, known_types[i].type))
1282 continue;
1284 plaintext = known_types[i].plain;
1285 goto plaintext_follow;
1288 xwin = ses->tab->term->environment & ENV_XWIN;
1289 handler = get_mime_type_handler(ctype, xwin);
1291 if (!handler && strlen(ctype) >= 4 && !strncasecmp(ctype, "text", 4))
1292 goto plaintext_follow;
1294 type_query = find_type_query(ses);
1295 if (type_query) {
1296 ret = 1;
1297 } else {
1298 type_query = init_type_query(ses, loading, cached);
1299 if (type_query) {
1300 ret = 1;
1301 #ifdef CONFIG_BITTORRENT
1302 /* A terrible waste of a good MIME handler here, but we want
1303 * to use the type_query this is easier. */
1304 if ((!strcasecmp(ctype, "application/x-bittorrent")
1305 || !strcasecmp(ctype, "application/x-torrent"))
1306 && !get_cmd_opt_bool("anonymous"))
1307 query_bittorrent_dialog(type_query);
1308 else
1309 #endif
1310 do_type_query(type_query, ctype, handler);
1314 mem_free_if(handler);
1316 return ret;
1318 plaintext_follow:
1319 vs = ses_forward(ses, frame);
1320 if (vs) vs->plain = plaintext;
1321 return 0;