move-link-prev-line: Really fixed.
[elinks.git] / src / session / download.c
blob56d58c8a34a802b2f42b3f406d4c9e27ed65edb9
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 "main/select.h"
36 #include "mime/mime.h"
37 #include "network/connection.h"
38 #include "network/progress.h"
39 #include "network/state.h"
40 #include "osdep/osdep.h"
41 #include "protocol/bittorrent/dialogs.h"
42 #include "protocol/date.h"
43 #include "protocol/protocol.h"
44 #include "protocol/uri.h"
45 #include "session/download.h"
46 #include "session/history.h"
47 #include "session/location.h"
48 #include "session/session.h"
49 #include "session/task.h"
50 #include "terminal/draw.h"
51 #include "terminal/screen.h"
52 #include "terminal/terminal.h"
53 #include "util/conv.h"
54 #include "util/error.h"
55 #include "util/file.h"
56 #include "util/lists.h"
57 #include "util/memlist.h"
58 #include "util/memory.h"
59 #include "util/string.h"
60 #include "util/time.h"
63 /* TODO: tp_*() should be in separate file, I guess? --pasky */
66 INIT_LIST_OF(struct file_download, downloads);
68 INIT_LIST_OF(struct popen_data, copiousoutput_data);
70 int
71 download_is_progressing(struct download *download)
73 return download
74 && download->state == S_TRANS
75 && has_progress(download->progress);
78 int
79 are_there_downloads(void)
81 struct file_download *file_download;
83 foreach (file_download, downloads)
84 if (!file_download->external_handler)
85 return 1;
87 return 0;
91 static void download_data(struct download *download, struct file_download *file_download);
93 struct file_download *
94 init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd)
96 struct file_download *file_download;
98 file_download = mem_calloc(1, sizeof(*file_download));
99 if (!file_download) return NULL;
101 /* Actually we could allow fragments in the URI and just change all the
102 * places that compares and shows the URI, but for now it is much
103 * easier this way. */
104 file_download->uri = get_composed_uri(uri, URI_BASE);
105 if (!file_download->uri) {
106 mem_free(file_download);
107 return NULL;
110 init_download_display(file_download);
112 file_download->file = file;
113 file_download->handle = fd;
115 file_download->download.callback = (download_callback_T *) download_data;
116 file_download->download.data = file_download;
117 file_download->ses = ses;
118 /* The tab may be closed, but we will still want to ie. open the
119 * handler on that terminal. */
120 file_download->term = ses->tab->term;
122 object_nolock(file_download, "file_download"); /* Debugging purpose. */
123 add_to_list(downloads, file_download);
125 return file_download;
129 void
130 abort_download(struct file_download *file_download)
132 #if 0
133 /* When hacking to cleanup the download code, remove lots of duplicated
134 * code and implement stuff from bug 435 we should reintroduce this
135 * assertion. Currently it will trigger often and shows that the
136 * download dialog code potentially could access free()d memory. */
137 assert(!is_object_used(file_download));
138 #endif
140 done_download_display(file_download);
142 if (file_download->ses)
143 check_questions_queue(file_download->ses);
145 if (file_download->dlg_data)
146 cancel_dialog(file_download->dlg_data, NULL);
147 cancel_download(&file_download->download, file_download->stop);
148 if (file_download->uri) done_uri(file_download->uri);
150 if (file_download->handle != -1) {
151 prealloc_truncate(file_download->handle, file_download->seek);
152 close(file_download->handle);
155 mem_free_if(file_download->external_handler);
156 if (file_download->file) {
157 if (file_download->delete) unlink(file_download->file);
158 mem_free(file_download->file);
160 del_from_list(file_download);
161 mem_free(file_download);
165 static void
166 kill_downloads_to_file(unsigned char *file)
168 struct file_download *file_download;
170 foreach (file_download, downloads) {
171 if (strcmp(file_download->file, file))
172 continue;
174 file_download = file_download->prev;
175 abort_download(file_download->next);
180 void
181 abort_all_downloads(void)
183 while (!list_empty(downloads))
184 abort_download(downloads.next);
188 void
189 destroy_downloads(struct session *ses)
191 struct file_download *file_download, *next;
192 struct session *s;
194 /* We are supposed to blat all downloads to external handlers belonging
195 * to @ses, but we will refuse to do so if there is another session
196 * bound to this terminal. That looks like the reasonable thing to do,
197 * fulfilling the principle of least astonishment. */
198 foreach (s, sessions) {
199 if (s == ses || s->tab->term != ses->tab->term)
200 continue;
202 foreach (file_download, downloads) {
203 if (file_download->ses != ses)
204 continue;
206 file_download->ses = s;
209 return;
212 foreachsafe (file_download, next, downloads) {
213 if (file_download->ses != ses)
214 continue;
216 if (!file_download->external_handler) {
217 file_download->ses = NULL;
218 continue;
221 abort_download(file_download);
226 static void
227 download_error_dialog(struct file_download *file_download, int saved_errno)
229 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
230 struct session *ses = file_download->ses;
231 struct terminal *term = file_download->term;
233 if (!ses) return;
235 info_box(term, MSGBOX_FREE_TEXT,
236 N_("Download error"), ALIGN_CENTER,
237 msg_text(term, N_("Could not create file '%s':\n%s"),
238 file_download->file, emsg));
241 static int
242 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
244 struct fragment *frag;
246 if (file_download->download.progress && file_download->download.progress->seek) {
247 file_download->seek = file_download->download.progress->seek;
248 file_download->download.progress->seek = 0;
249 /* This is exclusive with the prealloc, thus we can perform
250 * this in front of that thing safely. */
251 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
252 download_error_dialog(file_download, errno);
253 return 0;
257 foreach (frag, cached->frag) {
258 off_t remain = file_download->seek - frag->offset;
259 int *h = &file_download->handle;
260 ssize_t w;
262 if (remain < 0 || frag->length <= remain)
263 continue;
265 #ifdef USE_OPEN_PREALLOC
266 if (!file_download->seek
267 && (!file_download->download.progress
268 || file_download->download.progress->size > 0)) {
269 close(*h);
270 *h = open_prealloc(file_download->file,
271 O_CREAT|O_WRONLY|O_TRUNC,
272 0666,
273 file_download->download.progress
274 ? file_download->download.progress->size
275 : cached->length);
276 if (*h == -1) {
277 download_error_dialog(file_download, errno);
278 return 0;
280 set_bin(*h);
282 #endif
284 w = safe_write(*h, frag->data + remain, frag->length - remain);
285 if (w == -1) {
286 download_error_dialog(file_download, errno);
287 return 0;
290 file_download->seek += w;
293 return 1;
296 static void
297 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
299 if (term && get_opt_int("document.download.notify_bell")
300 + file_download->notify >= 2) {
301 beep_terminal(term);
304 abort_download(file_download);
307 static void
308 read_from_popen(struct session *ses, unsigned char *handler, unsigned char *filename)
310 FILE *stream = popen(handler, "r");
312 if (stream) {
313 int fd = fileno(stream);
315 if (fd > 0) {
316 unsigned char buf[48];
318 struct popen_data *data = mem_calloc(1, sizeof(*data));
320 if (!data) {
321 fclose(stream);
322 return;
324 data->fd = fd;
325 data->stream = stream;
326 if (filename) data->filename = stracpy(filename);
327 add_to_list(copiousoutput_data, data);
328 snprintf(buf, 48, "file:///dev/fd/%d", fd);
329 goto_url(ses, buf);
334 static void
335 download_data_store(struct download *download, struct file_download *file_download)
337 struct terminal *term = file_download->term;
339 if (!term) {
340 /* No term here, so no beep. --Zas */
341 abort_download(file_download);
342 return;
345 if (is_in_progress_state(download->state)) {
346 if (file_download->dlg_data)
347 redraw_dialog(file_download->dlg_data, 1);
348 return;
351 if (download->state != S_OK) {
352 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
353 enum connection_state state = download->state;
355 abort_download_and_beep(file_download, term);
357 if (!url) return;
359 info_box(term, MSGBOX_FREE_TEXT,
360 N_("Download error"), ALIGN_CENTER,
361 msg_text(term, N_("Error downloading %s:\n\n%s"),
362 url, get_state_message(state, term)));
363 mem_free(url);
364 return;
367 if (file_download->external_handler) {
368 prealloc_truncate(file_download->handle, file_download->seek);
369 close(file_download->handle);
370 file_download->handle = -1;
371 if (file_download->copiousoutput) {
372 read_from_popen(file_download->ses,
373 file_download->external_handler,
374 file_download->file);
375 file_download->delete = 0;
376 abort_download_and_beep(file_download, term);
377 } else {
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);
384 return;
387 if (file_download->notify) {
388 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
390 /* This is apparently a little racy. Deleting the box item will
391 * update the download browser _after_ the notification dialog
392 * has been drawn whereby it will be hidden. This should make
393 * the download browser update before launcing any
394 * notification. */
395 done_download_display(file_download);
397 if (url) {
398 info_box(term, MSGBOX_FREE_TEXT,
399 N_("Download"), ALIGN_CENTER,
400 msg_text(term, N_("Download complete:\n%s"), url));
401 mem_free(url);
405 if (file_download->remotetime
406 && get_opt_bool("document.download.set_original_time")) {
407 struct utimbuf foo;
409 foo.actime = foo.modtime = file_download->remotetime;
410 utime(file_download->file, &foo);
413 abort_download_and_beep(file_download, term);
416 static void
417 download_data(struct download *download, struct file_download *file_download)
419 struct cache_entry *cached = download->cached;
421 if (!cached || is_in_queued_state(download->state)) {
422 download_data_store(download, file_download);
423 return;
426 if (cached->last_modified)
427 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
429 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
430 cancel_download(&file_download->download, 0);
432 assertm(compare_uri(cached->uri, file_download->uri, 0),
433 "Redirecting using bad base URI");
435 done_uri(file_download->uri);
437 file_download->uri = get_uri_reference(cached->redirect);
438 file_download->download.state = S_WAIT_REDIR;
440 if (file_download->dlg_data)
441 redraw_dialog(file_download->dlg_data, 1);
443 load_uri(file_download->uri, cached->uri, &file_download->download,
444 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
445 download->progress ? download->progress->start : 0);
447 return;
450 if (!write_cache_entry_to_file(cached, file_download)) {
451 detach_connection(download, file_download->seek);
452 abort_download(file_download);
453 return;
456 detach_connection(download, file_download->seek);
457 download_data_store(download, file_download);
461 /* XXX: We assume that resume is everytime zero in lun's callbacks. */
462 struct lun_hop {
463 struct terminal *term;
464 unsigned char *ofile, *file;
466 void (*callback)(struct terminal *, unsigned char *, void *, int);
467 void *data;
470 enum {
471 COMMON_DOWNLOAD_DO = 0,
472 CONTINUE_DOWNLOAD_DO
475 struct cmdw_hop {
476 int magic; /* Must be first --witekfl */
477 struct session *ses;
478 unsigned char *real_file;
481 struct codw_hop {
482 int magic; /* must be first --witekfl */
483 struct type_query *type_query;
484 unsigned char *real_file;
485 unsigned char *file;
488 struct cdf_hop {
489 unsigned char **real_file;
490 int safe;
492 void (*callback)(struct terminal *, int, void *, int);
493 void *data;
496 static void
497 lun_alternate(void *lun_hop_)
499 struct lun_hop *lun_hop = lun_hop_;
501 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, 0);
502 mem_free_if(lun_hop->ofile);
503 mem_free(lun_hop);
506 static void
507 lun_cancel(void *lun_hop_)
509 struct lun_hop *lun_hop = lun_hop_;
511 lun_hop->callback(lun_hop->term, NULL, lun_hop->data, 0);
512 mem_free_if(lun_hop->ofile);
513 mem_free_if(lun_hop->file);
514 mem_free(lun_hop);
517 static void
518 lun_overwrite(void *lun_hop_)
520 struct lun_hop *lun_hop = lun_hop_;
522 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0);
523 mem_free_if(lun_hop->file);
524 mem_free(lun_hop);
527 static void common_download_do(struct terminal *term, int fd, void *data, int resume);
529 static void
530 lun_resume(void *lun_hop_)
532 struct lun_hop *lun_hop = lun_hop_;
533 struct cdf_hop *cdf_hop = lun_hop->data;
535 int magic = *(int *)cdf_hop->data;
537 if (magic == CONTINUE_DOWNLOAD_DO) {
538 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
540 if (!cmdw_hop) {
541 lun_cancel(lun_hop);
542 return;
543 } else {
544 struct codw_hop *codw_hop = cdf_hop->data;
545 struct type_query *type_query = codw_hop->type_query;
547 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
548 cmdw_hop->ses = type_query->ses;
549 /* FIXME: Current ses->download_uri is overwritten here --witekfl */
550 cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri);
552 if (type_query->external_handler) mem_free_if(codw_hop->file);
553 tp_cancel(type_query);
554 mem_free(codw_hop);
556 cdf_hop->real_file = &cmdw_hop->real_file;
557 cdf_hop->data = cmdw_hop;
558 cdf_hop->callback = common_download_do;
561 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 1);
562 mem_free_if(lun_hop->file);
563 mem_free(lun_hop);
567 static void
568 lookup_unique_name(struct terminal *term, unsigned char *ofile, int resume,
569 void (*callback)(struct terminal *, unsigned char *, void *, int),
570 void *data)
572 /* [gettext_accelerator_context(.lookup_unique_name)] */
573 struct lun_hop *lun_hop;
574 unsigned char *file;
575 int overwrite;
577 ofile = expand_tilde(ofile);
579 /* Minor code duplication to prevent useless call to get_opt_int()
580 * if possible. --Zas */
581 if (resume) {
582 callback(term, ofile, data, resume);
583 return;
586 /* !overwrite means always silently overwrite, which may be admitelly
587 * indeed a little confusing ;-) */
588 overwrite = get_opt_int("document.download.overwrite");
589 if (!overwrite) {
590 /* Nothing special to do... */
591 callback(term, ofile, data, resume);
592 return;
595 /* Check if file is a directory, and use a default name if it's the
596 * case. */
597 if (file_is_dir(ofile)) {
598 info_box(term, MSGBOX_FREE_TEXT,
599 N_("Download error"), ALIGN_CENTER,
600 msg_text(term, N_("'%s' is a directory."),
601 ofile));
602 mem_free(ofile);
603 callback(term, NULL, data, 0);
604 return;
607 /* Check if the file already exists (file != ofile). */
608 file = get_unique_name(ofile);
610 if (!file || overwrite == 1 || file == ofile) {
611 /* Still nothing special to do... */
612 if (file != ofile) mem_free(ofile);
613 callback(term, file, data, 0);
614 return;
617 /* overwrite == 2 (ask) and file != ofile (=> original file already
618 * exists) */
620 lun_hop = mem_calloc(1, sizeof(*lun_hop));
621 if (!lun_hop) {
622 if (file != ofile) mem_free(file);
623 mem_free(ofile);
624 callback(term, NULL, data, 0);
625 return;
627 lun_hop->term = term;
628 lun_hop->ofile = ofile;
629 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
630 lun_hop->callback = callback;
631 lun_hop->data = data;
633 msg_box(term, NULL, MSGBOX_FREE_TEXT,
634 N_("File exists"), ALIGN_CENTER,
635 msg_text(term, N_("This file already exists:\n"
636 "%s\n\n"
637 "The alternative filename is:\n"
638 "%s"),
639 empty_string_or_(lun_hop->ofile),
640 empty_string_or_(file)),
641 lun_hop, 4,
642 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
643 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
644 MSG_BOX_BUTTON(N_("~Resume download of the original file"), lun_resume, 0),
645 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
650 static void
651 create_download_file_do(struct terminal *term, unsigned char *file, void *data,
652 int resume)
654 struct cdf_hop *cdf_hop = data;
655 unsigned char *wd;
656 int h = -1;
657 int saved_errno;
658 #ifdef NO_FILE_SECURITY
659 int sf = 0;
660 #else
661 int sf = cdf_hop->safe;
662 #endif
664 if (!file) goto finish;
666 wd = get_cwd();
667 set_cwd(term->cwd);
669 /* Create parent directories if needed. */
670 mkalldirs(file);
672 /* O_APPEND means repositioning at the end of file before each write(),
673 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
674 * --pasky */
675 h = open(file, O_CREAT | O_WRONLY | (resume ? 0 : O_TRUNC)
676 | (sf && !resume ? O_EXCL : 0),
677 sf ? 0600 : 0666);
678 saved_errno = errno; /* Saved in case of ... --Zas */
680 if (wd) {
681 set_cwd(wd);
682 mem_free(wd);
685 if (h == -1) {
686 info_box(term, MSGBOX_FREE_TEXT,
687 N_("Download error"), ALIGN_CENTER,
688 msg_text(term, N_("Could not create file '%s':\n%s"),
689 file, strerror(saved_errno)));
691 mem_free(file);
692 goto finish;
694 } else {
695 set_bin(h);
697 if (!cdf_hop->safe) {
698 unsigned char *download_dir = get_opt_str("document.download.directory");
699 int i;
701 safe_strncpy(download_dir, file, MAX_STR_LEN);
703 /* Find the used directory so it's available in history */
704 for (i = strlen(download_dir); i >= 0; i--)
705 if (dir_sep(download_dir[i]))
706 break;
707 download_dir[i + 1] = 0;
711 if (cdf_hop->real_file)
712 *cdf_hop->real_file = file;
713 else
714 mem_free(file);
716 finish:
717 cdf_hop->callback(term, h, cdf_hop->data, resume);
718 mem_free(cdf_hop);
721 void
722 create_download_file(struct terminal *term, unsigned char *fi,
723 unsigned char **real_file, int safe, int resume,
724 void (*callback)(struct terminal *, int, void *, int),
725 void *data)
727 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
728 unsigned char *wd;
730 if (!cdf_hop) {
731 callback(term, -1, data, 0);
732 return;
735 cdf_hop->real_file = real_file;
736 cdf_hop->safe = safe;
737 cdf_hop->callback = callback;
738 cdf_hop->data = data;
740 /* FIXME: The wd bussiness is probably useless here? --pasky */
741 wd = get_cwd();
742 set_cwd(term->cwd);
744 /* Also the tilde will be expanded here. */
745 lookup_unique_name(term, fi, resume, create_download_file_do, cdf_hop);
747 if (wd) {
748 set_cwd(wd);
749 mem_free(wd);
754 static unsigned char *
755 get_temp_name(struct uri *uri)
757 struct string name;
758 unsigned char *extension;
759 /* FIXME
760 * We use tempnam() here, which is unsafe (race condition), for now.
761 * This should be changed at some time, but it needs an in-depth work
762 * of whole download code. --Zas */
763 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
765 if (!nm) return NULL;
767 if (!init_string(&name)) {
768 free(nm);
769 return NULL;
772 add_to_string(&name, nm);
773 free(nm);
775 extension = get_extension_from_uri(uri);
776 if (extension) {
777 add_shell_safe_to_string(&name, extension, strlen(extension));
778 mem_free(extension);
781 return name.source;
785 static unsigned char *
786 subst_file(unsigned char *prog, unsigned char *file)
788 struct string name;
789 /* When there is no %s in the mailcap entry, the handler program reads
790 * data from stdin instead of a file. */
791 int input = 1;
793 if (!init_string(&name)) return NULL;
795 while (*prog) {
796 int p;
798 for (p = 0; prog[p] && prog[p] != '%'; p++);
800 add_bytes_to_string(&name, prog, p);
801 prog += p;
803 if (*prog == '%') {
804 input = 0;
805 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
806 #ifdef MAX_PATH
807 unsigned char new_path[MAX_PATH];
808 #else
809 unsigned char new_path[1024];
810 #endif
812 cygwin_conv_to_full_win32_path(file, new_path);
813 add_to_string(&name, new_path);
814 #else
815 add_to_string(&name, file);
816 #endif
817 prog++;
821 if (input) {
822 struct string s;
824 if (init_string(&s)) {
825 add_to_string(&s, "/bin/cat ");
826 add_shell_quoted_to_string(&s, file, strlen(file));
827 add_to_string(&s, " | ");
828 add_string_to_string(&s, &name);
829 done_string(&name);
830 return s.source;
833 return name.source;
838 static void
839 common_download_do(struct terminal *term, int fd, void *data, int resume)
841 struct file_download *file_download;
842 struct cmdw_hop *cmdw_hop = data;
843 unsigned char *file = cmdw_hop->real_file;
844 struct session *ses = cmdw_hop->ses;
845 struct stat buf;
847 mem_free(cmdw_hop);
849 if (!file || fstat(fd, &buf)) return;
851 file_download = init_file_download(ses->download_uri, ses, file, fd);
852 if (!file_download) return;
854 if (resume) file_download->seek = buf.st_size;
856 display_download(ses->tab->term, file_download, ses);
858 load_uri(file_download->uri, ses->referrer, &file_download->download,
859 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
862 static void
863 common_download(struct session *ses, unsigned char *file, int resume)
865 struct cmdw_hop *cmdw_hop;
867 if (!ses->download_uri) return;
869 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
870 if (!cmdw_hop) return;
871 cmdw_hop->ses = ses;
872 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
874 kill_downloads_to_file(file);
876 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, 0,
877 resume, common_download_do, cmdw_hop);
880 void
881 start_download(void *ses, unsigned char *file)
883 common_download(ses, file, 0);
886 void
887 resume_download(void *ses, unsigned char *file)
889 common_download(ses, file, 1);
894 static void
895 continue_download_do(struct terminal *term, int fd, void *data, int resume)
897 struct codw_hop *codw_hop = data;
898 struct file_download *file_download = NULL;
899 struct type_query *type_query;
901 assert(codw_hop);
902 assert(codw_hop->type_query);
903 assert(codw_hop->type_query->uri);
904 assert(codw_hop->type_query->ses);
906 type_query = codw_hop->type_query;
907 if (!codw_hop->real_file) goto cancel;
909 file_download = init_file_download(type_query->uri, type_query->ses,
910 codw_hop->real_file, fd);
911 if (!file_download) goto cancel;
913 if (type_query->external_handler) {
914 file_download->external_handler = subst_file(type_query->external_handler,
915 codw_hop->file);
916 file_download->delete = 1;
917 file_download->copiousoutput = type_query->copiousoutput;
918 mem_free(codw_hop->file);
919 mem_free_set(&type_query->external_handler, NULL);
922 file_download->block = !!type_query->block;
924 /* Done here and not in init_file_download() so that the external
925 * handler can become initialized. */
926 display_download(term, file_download, type_query->ses);
928 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
929 done_type_query(type_query);
931 mem_free(codw_hop);
932 return;
934 cancel:
935 if (type_query->external_handler) mem_free_if(codw_hop->file);
936 tp_cancel(type_query);
937 mem_free(codw_hop);
940 static void
941 continue_download(void *data, unsigned char *file)
943 struct type_query *type_query = data;
944 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
946 if (!codw_hop) {
947 tp_cancel(type_query);
948 return;
951 if (type_query->external_handler) {
952 /* FIXME: get_temp_name() calls tempnam(). --Zas */
953 file = get_temp_name(type_query->uri);
954 if (!file) {
955 mem_free(codw_hop);
956 tp_cancel(type_query);
957 return;
961 codw_hop->type_query = type_query;
962 codw_hop->file = file;
963 codw_hop->magic = CONTINUE_DOWNLOAD_DO;
965 kill_downloads_to_file(file);
967 create_download_file(type_query->ses->tab->term, file,
968 &codw_hop->real_file,
969 !!type_query->external_handler, 0,
970 continue_download_do, codw_hop);
974 static struct type_query *
975 init_type_query(struct session *ses, struct download *download,
976 struct cache_entry *cached)
978 struct type_query *type_query;
980 /* There can be only one ... */
981 foreach (type_query, ses->type_queries)
982 if (compare_uri(type_query->uri, ses->loading_uri, 0))
983 return NULL;
985 type_query = mem_calloc(1, sizeof(*type_query));
986 if (!type_query) return NULL;
988 type_query->uri = get_uri_reference(ses->loading_uri);
989 type_query->ses = ses;
990 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
992 type_query->cached = cached;
993 object_lock(type_query->cached);
995 move_download(download, &type_query->download, PRI_MAIN);
996 download->state = S_OK;
998 add_to_list(ses->type_queries, type_query);
1000 return type_query;
1003 void
1004 done_type_query(struct type_query *type_query)
1006 /* Unregister any active download */
1007 cancel_download(&type_query->download, 0);
1009 object_unlock(type_query->cached);
1010 done_uri(type_query->uri);
1011 mem_free_if(type_query->external_handler);
1012 mem_free_if(type_query->target_frame);
1013 del_from_list(type_query);
1014 mem_free(type_query);
1018 void
1019 tp_cancel(void *data)
1021 struct type_query *type_query = data;
1023 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
1024 cancel_download(&type_query->download, 1);
1025 done_type_query(type_query);
1029 void
1030 tp_save(struct type_query *type_query)
1032 mem_free_set(&type_query->external_handler, NULL);
1033 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1036 /** This button handler uses the add_dlg_button() interface so that pressing
1037 * 'Show header' will not close the type query dialog. */
1038 static widget_handler_status_T
1039 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1041 struct type_query *type_query = widget_data->widget->data;
1043 cached_header_dialog(type_query->ses, type_query->cached);
1045 return EVENT_PROCESSED;
1049 /** @bug FIXME: We need to modify this function to take frame data
1050 * instead, as we want to use this function for frames as well (now,
1051 * when frame has content type text/plain, it is ignored and displayed
1052 * as HTML). */
1053 void
1054 tp_display(struct type_query *type_query)
1056 struct view_state *vs;
1057 struct session *ses = type_query->ses;
1058 struct uri *loading_uri = ses->loading_uri;
1059 unsigned char *target_frame = ses->task.target.frame;
1061 ses->loading_uri = type_query->uri;
1062 ses->task.target.frame = type_query->target_frame;
1063 vs = ses_forward(ses, /* type_query->frame */ 0);
1064 if (vs) vs->plain = 1;
1065 ses->loading_uri = loading_uri;
1066 ses->task.target.frame = target_frame;
1068 if (/* !type_query->frame */ 1) {
1069 struct download *old = &type_query->download;
1070 struct download *new = &cur_loc(ses)->download;
1072 new->callback = (download_callback_T *) doc_loading_callback;
1073 new->data = ses;
1075 move_download(old, new, PRI_MAIN);
1078 display_timer(ses);
1079 done_type_query(type_query);
1083 static void
1084 tp_open(struct type_query *type_query)
1086 if (!type_query->external_handler || !*type_query->external_handler) {
1087 tp_display(type_query);
1088 return;
1091 if (type_query->uri->protocol == PROTOCOL_FILE) {
1092 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1093 unsigned char *handler = NULL;
1095 if (file) {
1096 handler = subst_file(type_query->external_handler, file);
1097 mem_free(file);
1100 if (handler) {
1101 if (type_query->copiousoutput)
1102 read_from_popen(type_query->ses, handler, NULL);
1103 else
1104 exec_on_terminal(type_query->ses->tab->term,
1105 handler, "",
1106 type_query->block ? TERM_EXEC_FG : TERM_EXEC_BG);
1107 mem_free(handler);
1110 done_type_query(type_query);
1111 return;
1114 continue_download(type_query, "");
1118 static void
1119 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
1121 /* [gettext_accelerator_context(.do_type_query)] */
1122 struct string filename;
1123 unsigned char *description;
1124 unsigned char *desc_sep;
1125 unsigned char *format, *text, *title;
1126 struct dialog *dlg;
1127 #define TYPE_QUERY_WIDGETS_COUNT 8
1128 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1129 struct terminal *term = type_query->ses->tab->term;
1130 struct memory_list *ml;
1131 struct dialog_data *dlg_data;
1132 int selected_widget;
1134 mem_free_set(&type_query->external_handler, NULL);
1136 if (handler) {
1137 type_query->block = handler->block;
1138 if (!handler->ask) {
1139 type_query->external_handler = stracpy(handler->program);
1140 tp_open(type_query);
1141 return;
1144 /* Start preparing for the type query dialog. */
1145 description = handler->description;
1146 desc_sep = *description ? "; " : "";
1147 title = N_("What to do?");
1149 } else {
1150 title = N_("Unknown type");
1151 description = "";
1152 desc_sep = "";
1155 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1156 if (!dlg) return;
1158 if (init_string(&filename)) {
1159 add_mime_filename_to_string(&filename, type_query->uri);
1161 /* Let's make the filename pretty for display & save */
1162 /* TODO: The filename can be the empty string here. See bug 396. */
1163 #ifdef CONFIG_UTF8
1164 if (term->utf8_cp)
1165 decode_uri_string(&filename);
1166 else
1167 #endif /* CONFIG_UTF8 */
1168 decode_uri_string_for_display(&filename);
1171 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1172 /* For "default directory index pages" with wrong content-type
1173 * the filename can be NULL, e.g. http://www.spamhaus.org in bug 396. */
1174 if (filename.length) {
1175 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1176 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1177 } else {
1178 format = _("What would you like to do with the file (type: %s%s%s)?", term);
1179 snprintf(text, MAX_STR_LEN, format, ct, desc_sep, description);
1182 done_string(&filename);
1184 dlg->title = _(title, term);
1185 dlg->layouter = generic_dialog_layouter;
1186 dlg->layout.padding_top = 1;
1187 dlg->layout.fit_datalen = 1;
1188 dlg->udata2 = type_query;
1190 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1192 /* Add input field or text widget with info about the program handler. */
1193 if (!get_cmd_opt_bool("anonymous")) {
1194 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1196 if (!field) {
1197 mem_free(dlg);
1198 return;
1201 if (handler && handler->program) {
1202 safe_strncpy(field, handler->program, MAX_STR_LEN);
1205 /* xgettext:no-c-format */
1206 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1207 0, 0, NULL, MAX_STR_LEN, field, NULL);
1208 type_query->external_handler = field;
1210 if (type_query->copiousoutput) {
1211 add_dlg_text(dlg, _("The output of the program "
1212 "will be shown in the tab", term),
1213 ALIGN_LEFT, 0);
1214 } else {
1215 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1217 selected_widget = 3;
1219 } else if (handler) {
1220 unsigned char *field = text + MAX_STR_LEN;
1222 format = _("The file will be opened with the program '%s'.", term);
1223 snprintf(field, MAX_STR_LEN, format, handler->program);
1224 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1226 type_query->external_handler = stracpy(handler->program);
1227 if (!type_query->external_handler) {
1228 mem_free(dlg);
1229 return;
1232 widgets--;
1233 selected_widget = 2;
1235 } else {
1236 widgets -= 2;
1237 selected_widget = 1;
1240 /* Add buttons if they are both usable and allowed. */
1242 if (!get_cmd_opt_bool("anonymous") || handler) {
1243 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1244 (done_handler_T *) tp_open, type_query);
1245 } else {
1246 widgets--;
1249 if (!get_cmd_opt_bool("anonymous")) {
1250 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1251 (done_handler_T *) tp_save, type_query);
1252 } else {
1253 widgets--;
1256 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1257 (done_handler_T *) tp_display, type_query);
1259 if (type_query->cached && type_query->cached->head) {
1260 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1261 tp_show_header, type_query);
1262 } else {
1263 widgets--;
1266 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1267 (done_handler_T *) tp_cancel, type_query);
1269 add_dlg_end(dlg, widgets);
1271 ml = getml(dlg, (void *) NULL);
1272 if (!ml) {
1273 /* XXX: Assume that the allocated @external_handler will be
1274 * freed when releasing the @type_query. */
1275 mem_free(dlg);
1276 return;
1279 dlg_data = do_dialog(term, dlg, ml);
1280 /* Don't focus the text field; we want the user to be able
1281 * to select a button by typing the first letter of its label
1282 * without having to first leave the text field. */
1283 if (dlg_data) {
1284 select_widget_by_id(dlg_data, selected_widget);
1288 struct {
1289 unsigned char *type;
1290 unsigned int plain:1;
1291 } static const known_types[] = {
1292 { "text/html", 0 },
1293 { "text/plain", 1 },
1294 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1295 #if CONFIG_DOM
1296 { "application/docbook+xml", 1 },
1297 { "application/rss+xml", 0 },
1298 { "application/xbel+xml", 1 },
1299 { "application/xbel", 1 },
1300 { "application/x-xbel", 1 },
1301 #endif
1302 { NULL, 1 },
1306 setup_download_handler(struct session *ses, struct download *loading,
1307 struct cache_entry *cached, int frame)
1309 struct mime_handler *handler;
1310 struct view_state *vs;
1311 struct type_query *type_query;
1312 unsigned char *ctype = get_content_type(cached);
1313 int plaintext = 1;
1314 int ret = 0;
1315 int xwin, i;
1317 if (!ctype || !*ctype)
1318 goto plaintext_follow;
1320 for (i = 0; known_types[i].type; i++) {
1321 if (strcasecmp(ctype, known_types[i].type))
1322 continue;
1324 plaintext = known_types[i].plain;
1325 goto plaintext_follow;
1328 xwin = ses->tab->term->environment & ENV_XWIN;
1329 handler = get_mime_type_handler(ctype, xwin);
1331 if (!handler && strlen(ctype) >= 4 && !strncasecmp(ctype, "text", 4))
1332 goto plaintext_follow;
1334 type_query = init_type_query(ses, loading, cached);
1335 if (type_query) {
1336 ret = 1;
1337 if (handler) type_query->copiousoutput = handler->copiousoutput;
1338 #ifdef CONFIG_BITTORRENT
1339 /* A terrible waste of a good MIME handler here, but we want
1340 * to use the type_query this is easier. */
1341 if ((!strcasecmp(ctype, "application/x-bittorrent")
1342 || !strcasecmp(ctype, "application/x-torrent"))
1343 && !get_cmd_opt_bool("anonymous"))
1344 query_bittorrent_dialog(type_query);
1345 else
1346 #endif
1347 do_type_query(type_query, ctype, handler);
1350 mem_free_if(handler);
1352 return ret;
1354 plaintext_follow:
1355 vs = ses_forward(ses, frame);
1356 if (vs) vs->plain = plaintext;
1357 return 0;