Merge with http://elinks.cz/elinks.git
[elinks.git] / src / session / download.c
blobca56d74c678fc64748b6a33ce39e1ee93057d011
1 /* Downloads managment */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <errno.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #ifdef HAVE_SYS_CYGWIN_H
12 #include <sys/cygwin.h>
13 #endif
14 #include <sys/types.h>
15 #ifdef HAVE_FCNTL_H
16 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
17 #endif
18 #include <sys/stat.h>
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
22 #include <utime.h>
24 #include "elinks.h"
26 #include "bfu/dialog.h"
27 #include "cache/cache.h"
28 #include "config/options.h"
29 #include "dialogs/document.h"
30 #include "dialogs/download.h"
31 #include "dialogs/menu.h"
32 #include "intl/gettext/libintl.h"
33 #include "main/object.h"
34 #include "mime/mime.h"
35 #include "network/connection.h"
36 #include "network/progress.h"
37 #include "network/state.h"
38 #include "osdep/osdep.h"
39 #include "protocol/bittorrent/dialogs.h"
40 #include "protocol/date.h"
41 #include "protocol/protocol.h"
42 #include "protocol/uri.h"
43 #include "session/download.h"
44 #include "session/history.h"
45 #include "session/location.h"
46 #include "session/session.h"
47 #include "session/task.h"
48 #include "terminal/draw.h"
49 #include "terminal/screen.h"
50 #include "terminal/terminal.h"
51 #include "util/conv.h"
52 #include "util/error.h"
53 #include "util/file.h"
54 #include "util/lists.h"
55 #include "util/memlist.h"
56 #include "util/memory.h"
57 #include "util/string.h"
58 #include "util/time.h"
61 /* TODO: tp_*() should be in separate file, I guess? --pasky */
64 INIT_LIST_HEAD(downloads);
66 int
67 download_is_progressing(struct download *download)
69 return download
70 && download->state == S_TRANS
71 && has_progress(download->progress);
74 int
75 are_there_downloads(void)
77 struct file_download *file_download;
79 foreach (file_download, downloads)
80 if (!file_download->external_handler)
81 return 1;
83 return 0;
87 static void download_data(struct download *download, struct file_download *file_download);
89 struct file_download *
90 init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd)
92 struct file_download *file_download;
94 file_download = mem_calloc(1, sizeof(*file_download));
95 if (!file_download) return NULL;
97 /* Actually we could allow fragments in the URI and just change all the
98 * places that compares and shows the URI, but for now it is much
99 * easier this way. */
100 file_download->uri = get_composed_uri(uri, URI_BASE);
101 if (!file_download->uri) {
102 mem_free(file_download);
103 return NULL;
106 init_download_display(file_download);
108 file_download->file = file;
109 file_download->handle = fd;
111 file_download->download.callback = (download_callback_T *) download_data;
112 file_download->download.data = file_download;
113 file_download->ses = ses;
114 /* The tab may be closed, but we will still want to ie. open the
115 * handler on that terminal. */
116 file_download->term = ses->tab->term;
118 object_nolock(file_download, "file_download"); /* Debugging purpose. */
119 add_to_list(downloads, file_download);
121 return file_download;
125 void
126 abort_download(struct file_download *file_download)
128 #if 0
129 /* When hacking to cleanup the download code, remove lots of duplicated
130 * code and implement stuff from bug 435 we should reintroduce this
131 * assertion. Currently it will trigger often and shows that the
132 * download dialog code potentially could access free()d memory. */
133 assert(!is_object_used(file_download));
134 #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 if (is_in_progress_state(file_download->download.state))
144 change_connection(&file_download->download, NULL, PRI_CANCEL,
145 file_download->stop);
146 if (file_download->uri) done_uri(file_download->uri);
148 if (file_download->handle != -1) {
149 prealloc_truncate(file_download->handle, file_download->seek);
150 close(file_download->handle);
153 mem_free_if(file_download->external_handler);
154 if (file_download->file) {
155 if (file_download->delete) unlink(file_download->file);
156 mem_free(file_download->file);
158 del_from_list(file_download);
159 mem_free(file_download);
163 static void
164 kill_downloads_to_file(unsigned char *file)
166 struct file_download *file_download;
168 foreach (file_download, downloads) {
169 if (strcmp(file_download->file, file))
170 continue;
172 file_download = file_download->prev;
173 abort_download(file_download->next);
178 void
179 abort_all_downloads(void)
181 while (!list_empty(downloads))
182 abort_download(downloads.next);
186 void
187 destroy_downloads(struct session *ses)
189 struct file_download *file_download, *next;
190 struct session *s;
192 /* We are supposed to blat all downloads to external handlers belonging
193 * to @ses, but we will refuse to do so if there is another session
194 * bound to this terminal. That looks like the reasonable thing to do,
195 * fulfilling the principle of least astonishment. */
196 foreach (s, sessions) {
197 if (s == ses || s->tab->term != ses->tab->term)
198 continue;
200 foreach (file_download, downloads) {
201 if (file_download->ses != ses)
202 continue;
204 file_download->ses = s;
207 return;
210 foreachsafe (file_download, next, downloads) {
211 if (file_download->ses != ses)
212 continue;
214 if (!file_download->external_handler) {
215 file_download->ses = NULL;
216 continue;
219 abort_download(file_download);
224 static void
225 download_error_dialog(struct file_download *file_download, int saved_errno)
227 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
228 struct session *ses = file_download->ses;
229 struct terminal *term = file_download->term;
231 if (!ses) return;
233 info_box(term, MSGBOX_FREE_TEXT,
234 N_("Download error"), ALIGN_CENTER,
235 msg_text(term, N_("Could not create file '%s':\n%s"),
236 file_download->file, emsg));
239 static int
240 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
242 struct fragment *frag;
244 if (file_download->download.progress && file_download->download.progress->seek) {
245 file_download->seek = file_download->download.progress->seek;
246 file_download->download.progress->seek = 0;
247 /* This is exclusive with the prealloc, thus we can perform
248 * this in front of that thing safely. */
249 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
250 download_error_dialog(file_download, errno);
251 return 0;
255 foreach (frag, cached->frag) {
256 off_t remain = file_download->seek - frag->offset;
257 int *h = &file_download->handle;
258 ssize_t w;
260 if (remain < 0 || frag->length <= remain)
261 continue;
263 #ifdef USE_OPEN_PREALLOC
264 if (!file_download->seek
265 && (!file_download->download.progress
266 || file_download->download.progress->size > 0)) {
267 close(*h);
268 *h = open_prealloc(file_download->file,
269 O_CREAT|O_WRONLY|O_TRUNC,
270 0666,
271 file_download->download.progress
272 ? file_download->download.progress->size
273 : cached->length);
274 if (*h == -1) {
275 download_error_dialog(file_download, errno);
276 return 0;
278 set_bin(*h);
280 #endif
282 w = safe_write(*h, frag->data + remain, frag->length - remain);
283 if (w == -1) {
284 download_error_dialog(file_download, errno);
285 return 0;
288 file_download->seek += w;
291 return 1;
294 static void
295 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
297 if (term && get_opt_int("document.download.notify_bell")
298 + file_download->notify >= 2) {
299 beep_terminal(term);
302 abort_download(file_download);
305 static void
306 download_data_store(struct download *download, struct file_download *file_download)
308 struct terminal *term = file_download->term;
310 if (!term) {
311 /* No term here, so no beep. --Zas */
312 abort_download(file_download);
313 return;
316 if (is_in_progress_state(download->state)) {
317 if (file_download->dlg_data)
318 redraw_dialog(file_download->dlg_data, 1);
319 return;
322 if (download->state != S_OK) {
323 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
324 enum connection_state state = download->state;
326 abort_download_and_beep(file_download, term);
328 if (!url) return;
330 info_box(term, MSGBOX_FREE_TEXT,
331 N_("Download error"), ALIGN_CENTER,
332 msg_text(term, N_("Error downloading %s:\n\n%s"),
333 url, get_state_message(state, term)));
334 mem_free(url);
335 return;
338 if (file_download->external_handler) {
339 prealloc_truncate(file_download->handle, file_download->seek);
340 close(file_download->handle);
341 file_download->handle = -1;
342 exec_on_terminal(term, file_download->external_handler,
343 file_download->file,
344 !!file_download->block);
345 file_download->delete = 0;
346 abort_download_and_beep(file_download, term);
347 return;
350 if (file_download->notify) {
351 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
353 /* This is apparently a little racy. Deleting the box item will
354 * update the download browser _after_ the notification dialog
355 * has been drawn whereby it will be hidden. This should make
356 * the download browser update before launcing any
357 * notification. */
358 done_download_display(file_download);
360 if (url) {
361 info_box(term, MSGBOX_FREE_TEXT,
362 N_("Download"), ALIGN_CENTER,
363 msg_text(term, N_("Download complete:\n%s"), url));
364 mem_free(url);
368 if (file_download->remotetime
369 && get_opt_bool("document.download.set_original_time")) {
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 if (is_in_progress_state(download->state))
394 change_connection(&file_download->download, NULL, PRI_CANCEL, 0);
396 assertm(compare_uri(cached->uri, file_download->uri, 0),
397 "Redirecting using bad base URI");
399 done_uri(file_download->uri);
401 file_download->uri = get_uri_reference(cached->redirect);
402 file_download->download.state = S_WAIT_REDIR;
404 if (file_download->dlg_data)
405 redraw_dialog(file_download->dlg_data, 1);
407 load_uri(file_download->uri, cached->uri, &file_download->download,
408 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
409 download->progress ? download->progress->start : 0);
411 return;
414 if (!write_cache_entry_to_file(cached, file_download)) {
415 detach_connection(download, file_download->seek);
416 abort_download(file_download);
417 return;
420 detach_connection(download, file_download->seek);
421 download_data_store(download, file_download);
425 /* XXX: We assume that resume is everytime zero in lun's callbacks. */
426 struct lun_hop {
427 struct terminal *term;
428 unsigned char *ofile, *file;
430 void (*callback)(struct terminal *, unsigned char *, void *, int);
431 void *data;
434 static void
435 lun_alternate(struct lun_hop *lun_hop)
437 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, 0);
438 mem_free_if(lun_hop->ofile);
439 mem_free(lun_hop);
442 static void
443 lun_overwrite(struct lun_hop *lun_hop)
445 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0);
446 mem_free_if(lun_hop->file);
447 mem_free(lun_hop);
450 static void
451 lun_resume(struct lun_hop *lun_hop)
453 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 1);
454 mem_free_if(lun_hop->file);
455 mem_free(lun_hop);
458 static void
459 lun_cancel(struct lun_hop *lun_hop)
461 lun_hop->callback(lun_hop->term, NULL, lun_hop->data, 0);
462 mem_free_if(lun_hop->ofile);
463 mem_free_if(lun_hop->file);
464 mem_free(lun_hop);
467 static void
468 lookup_unique_name(struct terminal *term, unsigned char *ofile, int resume,
469 void (*callback)(struct terminal *, unsigned char *, void *, int),
470 void *data)
472 /* [gettext_accelerator_context(.lookup_unique_name)] */
473 struct lun_hop *lun_hop;
474 unsigned char *file;
475 int overwrite;
477 ofile = expand_tilde(ofile);
479 /* Minor code duplication to prevent useless call to get_opt_int()
480 * if possible. --Zas */
481 if (resume) {
482 callback(term, ofile, data, resume);
483 return;
486 /* !overwrite means always silently overwrite, which may be admitelly
487 * indeed a little confusing ;-) */
488 overwrite = get_opt_int("document.download.overwrite");
489 if (!overwrite) {
490 /* Nothing special to do... */
491 callback(term, ofile, data, resume);
492 return;
495 /* Check if file is a directory, and use a default name if it's the
496 * case. */
497 if (file_is_dir(ofile)) {
498 info_box(term, MSGBOX_FREE_TEXT,
499 N_("Download error"), ALIGN_CENTER,
500 msg_text(term, N_("'%s' is a directory."),
501 ofile));
502 mem_free(ofile);
503 callback(term, NULL, data, 0);
504 return;
507 /* Check if the file already exists (file != ofile). */
508 file = get_unique_name(ofile);
510 if (!file || overwrite == 1 || file == ofile) {
511 /* Still nothing special to do... */
512 if (file != ofile) mem_free(ofile);
513 callback(term, file, data, 0);
514 return;
517 /* overwrite == 2 (ask) and file != ofile (=> original file already
518 * exists) */
520 lun_hop = mem_calloc(1, sizeof(*lun_hop));
521 if (!lun_hop) {
522 if (file != ofile) mem_free(file);
523 mem_free(ofile);
524 callback(term, NULL, data, 0);
525 return;
527 lun_hop->term = term;
528 lun_hop->ofile = ofile;
529 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
530 lun_hop->callback = callback;
531 lun_hop->data = data;
533 msg_box(term, NULL, MSGBOX_FREE_TEXT,
534 N_("File exists"), ALIGN_CENTER,
535 msg_text(term, N_("This file already exists:\n"
536 "%s\n\n"
537 "The alternative filename is:\n"
538 "%s"),
539 empty_string_or_(lun_hop->ofile),
540 empty_string_or_(file)),
541 lun_hop, 4,
542 N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER,
543 N_("~Overwrite the original file"), lun_overwrite, 0,
544 N_("~Resume download of the original file"), lun_resume, 0,
545 N_("~Cancel"), lun_cancel, B_ESC);
549 struct cdf_hop {
550 unsigned char **real_file;
551 int safe;
553 void (*callback)(struct terminal *, int, void *, int);
554 void *data;
557 static void
558 create_download_file_do(struct terminal *term, unsigned char *file, void *data,
559 int resume)
561 struct cdf_hop *cdf_hop = data;
562 unsigned char *wd;
563 int h = -1;
564 int saved_errno;
565 #ifdef NO_FILE_SECURITY
566 int sf = 0;
567 #else
568 int sf = cdf_hop->safe;
569 #endif
571 if (!file) goto finish;
573 wd = get_cwd();
574 set_cwd(term->cwd);
576 /* O_APPEND means repositioning at the end of file before each write(),
577 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
578 * --pasky */
579 h = open(file, O_CREAT | O_WRONLY | (resume ? 0 : O_TRUNC)
580 | (sf && !resume ? O_EXCL : 0),
581 sf ? 0600 : 0666);
582 saved_errno = errno; /* Saved in case of ... --Zas */
584 if (wd) {
585 set_cwd(wd);
586 mem_free(wd);
589 if (h == -1) {
590 info_box(term, MSGBOX_FREE_TEXT,
591 N_("Download error"), ALIGN_CENTER,
592 msg_text(term, N_("Could not create file '%s':\n%s"),
593 file, strerror(saved_errno)));
595 mem_free(file);
596 goto finish;
598 } else {
599 set_bin(h);
601 if (!cdf_hop->safe) {
602 unsigned char *download_dir = get_opt_str("document.download.directory");
603 int i;
605 safe_strncpy(download_dir, file, MAX_STR_LEN);
607 /* Find the used directory so it's available in history */
608 for (i = strlen(download_dir); i >= 0; i--)
609 if (dir_sep(download_dir[i]))
610 break;
611 download_dir[i + 1] = 0;
615 if (cdf_hop->real_file)
616 *cdf_hop->real_file = file;
617 else
618 mem_free(file);
620 finish:
621 cdf_hop->callback(term, h, cdf_hop->data, resume);
622 mem_free(cdf_hop);
625 void
626 create_download_file(struct terminal *term, unsigned char *fi,
627 unsigned char **real_file, int safe, int resume,
628 void (*callback)(struct terminal *, int, void *, int),
629 void *data)
631 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
632 unsigned char *wd;
634 if (!cdf_hop) {
635 callback(term, -1, data, 0);
636 return;
639 cdf_hop->real_file = real_file;
640 cdf_hop->safe = safe;
641 cdf_hop->callback = callback;
642 cdf_hop->data = data;
644 /* FIXME: The wd bussiness is probably useless here? --pasky */
645 wd = get_cwd();
646 set_cwd(term->cwd);
648 /* Also the tilde will be expanded here. */
649 lookup_unique_name(term, fi, resume, create_download_file_do, cdf_hop);
651 if (wd) {
652 set_cwd(wd);
653 mem_free(wd);
658 static unsigned char *
659 get_temp_name(struct uri *uri)
661 struct string name;
662 unsigned char *extension;
663 /* FIXME
664 * We use tempnam() here, which is unsafe (race condition), for now.
665 * This should be changed at some time, but it needs an in-depth work
666 * of whole download code. --Zas */
667 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
669 if (!nm) return NULL;
671 if (!init_string(&name)) {
672 free(nm);
673 return NULL;
676 add_to_string(&name, nm);
677 free(nm);
679 extension = get_extension_from_uri(uri);
680 if (extension) {
681 add_shell_safe_to_string(&name, extension, strlen(extension));
682 mem_free(extension);
685 return name.source;
689 static unsigned char *
690 subst_file(unsigned char *prog, unsigned char *file)
692 struct string name;
694 if (!init_string(&name)) return NULL;
696 while (*prog) {
697 int p;
699 for (p = 0; prog[p] && prog[p] != '%'; p++);
701 add_bytes_to_string(&name, prog, p);
702 prog += p;
704 if (*prog == '%') {
705 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
706 #ifdef MAX_PATH
707 unsigned char new_path[MAX_PATH];
708 #else
709 unsigned char new_path[1024];
710 #endif
712 cygwin_conv_to_full_win32_path(file, new_path);
713 add_to_string(&name, new_path);
714 #else
715 add_to_string(&name, file);
716 #endif
717 prog++;
721 return name.source;
725 struct cmdw_hop {
726 struct session *ses;
727 unsigned char *real_file;
730 static void
731 common_download_do(struct terminal *term, int fd, void *data, int resume)
733 struct file_download *file_download;
734 struct cmdw_hop *cmdw_hop = data;
735 unsigned char *file = cmdw_hop->real_file;
736 struct session *ses = cmdw_hop->ses;
737 struct stat buf;
739 mem_free(cmdw_hop);
741 if (!file || fstat(fd, &buf)) return;
743 file_download = init_file_download(ses->download_uri, ses, file, fd);
744 if (!file_download) return;
746 if (resume) file_download->seek = buf.st_size;
748 display_download(ses->tab->term, file_download, ses);
750 load_uri(file_download->uri, ses->referrer, &file_download->download,
751 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
754 static void
755 common_download(struct session *ses, unsigned char *file, int resume)
757 struct cmdw_hop *cmdw_hop;
759 if (!ses->download_uri) return;
761 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
762 if (!cmdw_hop) return;
763 cmdw_hop->ses = ses;
765 kill_downloads_to_file(file);
767 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, 0,
768 resume, common_download_do, cmdw_hop);
771 void
772 start_download(void *ses, unsigned char *file)
774 common_download(ses, file, 0);
777 void
778 resume_download(void *ses, unsigned char *file)
780 common_download(ses, file, 1);
784 struct codw_hop {
785 struct type_query *type_query;
786 unsigned char *real_file;
787 unsigned char *file;
790 static void
791 continue_download_do(struct terminal *term, int fd, void *data, int resume)
793 struct codw_hop *codw_hop = data;
794 struct file_download *file_download = NULL;
795 struct type_query *type_query;
797 assert(codw_hop);
798 assert(codw_hop->type_query);
799 assert(codw_hop->type_query->uri);
800 assert(codw_hop->type_query->ses);
802 type_query = codw_hop->type_query;
803 if (!codw_hop->real_file) goto cancel;
805 file_download = init_file_download(type_query->uri, type_query->ses,
806 codw_hop->real_file, fd);
807 if (!file_download) goto cancel;
809 if (type_query->external_handler) {
810 file_download->external_handler = subst_file(type_query->external_handler,
811 codw_hop->file);
812 file_download->delete = 1;
813 mem_free(codw_hop->file);
814 mem_free_set(&type_query->external_handler, NULL);
817 file_download->block = !!type_query->block;
819 /* Done here and not in init_file_download() so that the external
820 * handler can become initialized. */
821 display_download(term, file_download, type_query->ses);
823 change_connection(&type_query->download, &file_download->download, PRI_DOWNLOAD, 0);
824 done_type_query(type_query);
826 mem_free(codw_hop);
827 return;
829 cancel:
830 if (type_query->external_handler) mem_free_if(codw_hop->file);
831 tp_cancel(type_query);
832 mem_free(codw_hop);
835 static void
836 continue_download(void *data, unsigned char *file)
838 struct type_query *type_query = data;
839 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
841 if (!codw_hop) {
842 tp_cancel(type_query);
843 return;
846 if (type_query->external_handler) {
847 /* FIXME: get_temp_name() calls tempnam(). --Zas */
848 file = get_temp_name(type_query->uri);
849 if (!file) {
850 mem_free(codw_hop);
851 tp_cancel(type_query);
852 return;
856 codw_hop->type_query = type_query;
857 codw_hop->file = file;
859 kill_downloads_to_file(file);
861 create_download_file(type_query->ses->tab->term, file,
862 &codw_hop->real_file,
863 !!type_query->external_handler, 0,
864 continue_download_do, codw_hop);
868 static struct type_query *
869 init_type_query(struct session *ses, struct download *download,
870 struct cache_entry *cached)
872 struct type_query *type_query;
874 /* There can be only one ... */
875 foreach (type_query, ses->type_queries)
876 if (compare_uri(type_query->uri, ses->loading_uri, 0))
877 return NULL;
879 type_query = mem_calloc(1, sizeof(*type_query));
880 if (!type_query) return NULL;
882 type_query->uri = get_uri_reference(ses->loading_uri);
883 type_query->ses = ses;
884 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
886 type_query->cached = cached;
887 object_lock(type_query->cached);
889 change_connection(download, &type_query->download, PRI_MAIN, 0);
890 download->state = S_OK;
892 add_to_list(ses->type_queries, type_query);
894 return type_query;
897 void
898 done_type_query(struct type_query *type_query)
900 /* Unregister any active download */
901 if (is_in_progress_state(type_query->download.state))
902 change_connection(&type_query->download, NULL, PRI_CANCEL, 0);
904 object_unlock(type_query->cached);
905 done_uri(type_query->uri);
906 mem_free_if(type_query->external_handler);
907 mem_free_if(type_query->target_frame);
908 del_from_list(type_query);
909 mem_free(type_query);
913 void
914 tp_cancel(void *data)
916 struct type_query *type_query = data;
917 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
918 change_connection(&type_query->download, NULL, PRI_CANCEL, 1);
919 done_type_query(type_query);
923 void
924 tp_save(struct type_query *type_query)
926 mem_free_set(&type_query->external_handler, NULL);
927 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
930 /* This button handler uses the add_dlg_button() interface so that pressing
931 * 'Show header' will not close the type query dialog. */
932 static widget_handler_status_T
933 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
935 struct type_query *type_query = widget_data->widget->data;
937 cached_header_dialog(type_query->ses, type_query->cached);
939 return EVENT_PROCESSED;
943 /* FIXME: We need to modify this function to take frame data instead, as we
944 * want to use this function for frames as well (now, when frame has content
945 * type text/plain, it is ignored and displayed as HTML). */
946 void
947 tp_display(struct type_query *type_query)
949 struct view_state *vs;
950 struct session *ses = type_query->ses;
951 struct uri *loading_uri = ses->loading_uri;
952 unsigned char *target_frame = ses->task.target.frame;
954 ses->loading_uri = type_query->uri;
955 ses->task.target.frame = type_query->target_frame;
956 vs = ses_forward(ses, /* type_query->frame */ 0);
957 if (vs) vs->plain = 1;
958 ses->loading_uri = loading_uri;
959 ses->task.target.frame = target_frame;
961 if (/* !type_query->frame */ 1) {
962 struct download *old = &type_query->download;
963 struct download *new = &cur_loc(ses)->download;
965 new->callback = (download_callback_T *) doc_loading_callback;
966 new->data = ses;
968 if (is_in_progress_state(old->state))
969 change_connection(old, new, PRI_MAIN, 0);
970 else
971 new->state = old->state;
974 display_timer(ses);
975 done_type_query(type_query);
978 static void
979 tp_open(struct type_query *type_query)
981 if (!type_query->external_handler || !*type_query->external_handler) {
982 tp_display(type_query);
983 return;
986 continue_download(type_query, "");
990 static void
991 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
993 /* [gettext_accelerator_context(.do_type_query)] */
994 struct string filename;
995 unsigned char *description;
996 unsigned char *desc_sep;
997 unsigned char *format, *text, *title;
998 struct dialog *dlg;
999 #define TYPE_QUERY_WIDGETS_COUNT 8
1000 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1001 struct terminal *term = type_query->ses->tab->term;
1002 struct memory_list *ml;
1003 struct dialog_data *dlg_data;
1004 int selected_widget;
1006 mem_free_set(&type_query->external_handler, NULL);
1008 if (handler) {
1009 type_query->block = handler->block;
1010 if (!handler->ask) {
1011 type_query->external_handler = stracpy(handler->program);
1012 tp_open(type_query);
1013 return;
1016 /* Start preparing for the type query dialog. */
1017 description = handler->description;
1018 desc_sep = *description ? "; " : "";
1019 title = N_("What to do?");
1021 } else {
1022 title = N_("Unknown type");
1023 description = "";
1024 desc_sep = "";
1027 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1028 if (!dlg) return;
1030 if (init_string(&filename)) {
1031 add_mime_filename_to_string(&filename, type_query->uri);
1033 /* Let's make the filename pretty for display & save */
1034 /* TODO: The filename can be the empty string here. See bug 396. */
1035 decode_uri_string_for_display(&filename);
1038 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1039 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1040 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1042 done_string(&filename);
1044 dlg->title = _(title, term);
1045 dlg->layouter = generic_dialog_layouter;
1046 dlg->layout.padding_top = 1;
1047 dlg->layout.fit_datalen = 1;
1048 dlg->udata2 = type_query;
1050 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1052 /* Add input field or text widget with info about the program handler. */
1053 if (!get_cmd_opt_bool("anonymous")) {
1054 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1056 if (!field) {
1057 mem_free(dlg);
1058 return;
1061 if (handler && handler->program) {
1062 int programlen = strlen(handler->program);
1064 programlen = int_max(programlen, MAX_STR_LEN);
1065 memcpy(field, handler->program, programlen);
1068 /* xgettext:no-c-format */
1069 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1070 0, 0, NULL, MAX_STR_LEN, field, NULL);
1071 type_query->external_handler = field;
1073 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1074 selected_widget = 3;
1076 } else if (handler) {
1077 unsigned char *field = text + MAX_STR_LEN;
1079 format = _("The file will be opened with the program '%s'.", term);
1080 snprintf(field, MAX_STR_LEN, format, handler->program);
1081 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1083 type_query->external_handler = stracpy(handler->program);
1084 if (!type_query->external_handler) {
1085 mem_free(dlg);
1086 return;
1089 widgets--;
1090 selected_widget = 2;
1092 } else {
1093 widgets -= 2;
1094 selected_widget = 1;
1097 /* Add buttons if they are both usable and allowed. */
1099 if (!get_cmd_opt_bool("anonymous") || handler) {
1100 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1101 (done_handler_T *) tp_open, type_query);
1102 } else {
1103 widgets--;
1106 if (!get_cmd_opt_bool("anonymous")) {
1107 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1108 (done_handler_T *) tp_save, type_query);
1109 } else {
1110 widgets--;
1113 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1114 (done_handler_T *) tp_display, type_query);
1116 if (type_query->cached && type_query->cached->head) {
1117 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1118 tp_show_header, type_query);
1119 } else {
1120 widgets--;
1123 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1124 (done_handler_T *) tp_cancel, type_query);
1126 add_dlg_end(dlg, widgets);
1128 ml = getml(dlg, NULL);
1129 if (!ml) {
1130 /* XXX: Assume that the allocated @external_handler will be
1131 * freed when releasing the @type_query. */
1132 mem_free(dlg);
1133 return;
1136 dlg_data = do_dialog(term, dlg, ml);
1137 /* Don't focus the text field; we want the user to be able
1138 * to select a button by typing the first letter of its label
1139 * without having to first leave the text field. */
1140 if (dlg_data) {
1141 select_widget_by_id(dlg_data, selected_widget);
1145 struct {
1146 unsigned char *type;
1147 unsigned int plain:1;
1148 } static known_types[] = {
1149 { "text/html", 0 },
1150 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1151 #if CONFIG_DOM
1152 { "application/rss+xml", 1 },
1153 { "application/xbel+xml", 1 },
1154 { "application/xbel", 1 },
1155 { "application/x-xbel", 1 },
1156 #endif
1157 { "text/plain", 1 },
1158 { NULL, 1 },
1162 setup_download_handler(struct session *ses, struct download *loading,
1163 struct cache_entry *cached, int frame)
1165 struct mime_handler *handler;
1166 struct view_state *vs;
1167 struct type_query *type_query;
1168 unsigned char *ctype = get_content_type(cached);
1169 int plaintext = 1;
1170 int ret = 0;
1171 int xwin, i;
1173 if (!ctype || !*ctype)
1174 goto plaintext_follow;
1176 for (i = 0; known_types[i].type; i++) {
1177 if (strcasecmp(ctype, known_types[i].type))
1178 continue;
1180 plaintext = known_types[i].plain;
1181 goto plaintext_follow;
1184 xwin = ses->tab->term->environment & ENV_XWIN;
1185 handler = get_mime_type_handler(ctype, xwin);
1187 if (!handler && strlen(ctype) >= 4 && !strncasecmp(ctype, "text", 4))
1188 goto plaintext_follow;
1190 type_query = init_type_query(ses, loading, cached);
1191 if (type_query) {
1192 ret = 1;
1193 #ifdef CONFIG_BITTORRENT
1194 /* A terrible waste of a good MIME handler here, but we want
1195 * to use the type_query this is easier. */
1196 if ((!strcasecmp(ctype, "application/x-bittorrent")
1197 || !strcasecmp(ctype, "application/x-torrent"))
1198 && !get_cmd_opt_bool("anonymous"))
1199 query_bittorrent_dialog(type_query);
1200 else
1201 #endif
1202 do_type_query(type_query, ctype, handler);
1205 mem_free_if(handler);
1207 return ret;
1209 plaintext_follow:
1210 vs = ses_forward(ses, frame);
1211 if (vs) vs->plain = plaintext;
1212 return 0;