Added test for uploading big files.
[elinks.git] / src / session / download.c
blobe2b5d70dfdb255445f17eb13d35707aeb147ac64
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
139 done_download_display(file_download);
141 if (file_download->ses)
142 check_questions_queue(file_download->ses);
144 if (file_download->dlg_data)
145 cancel_dialog(file_download->dlg_data, NULL);
146 cancel_download(&file_download->download, file_download->stop);
147 if (file_download->uri) done_uri(file_download->uri);
149 if (file_download->handle != -1) {
150 prealloc_truncate(file_download->handle, file_download->seek);
151 close(file_download->handle);
154 mem_free_if(file_download->external_handler);
155 if (file_download->file) {
156 if (file_download->delete) unlink(file_download->file);
157 mem_free(file_download->file);
159 del_from_list(file_download);
160 mem_free(file_download);
164 static void
165 kill_downloads_to_file(unsigned char *file)
167 struct file_download *file_download;
169 foreach (file_download, downloads) {
170 if (strcmp(file_download->file, file))
171 continue;
173 file_download = file_download->prev;
174 abort_download(file_download->next);
179 void
180 abort_all_downloads(void)
182 while (!list_empty(downloads))
183 abort_download(downloads.next);
187 void
188 destroy_downloads(struct session *ses)
190 struct file_download *file_download, *next;
191 struct session *s;
193 /* We are supposed to blat all downloads to external handlers belonging
194 * to @ses, but we will refuse to do so if there is another session
195 * bound to this terminal. That looks like the reasonable thing to do,
196 * fulfilling the principle of least astonishment. */
197 foreach (s, sessions) {
198 if (s == ses || s->tab->term != ses->tab->term)
199 continue;
201 foreach (file_download, downloads) {
202 if (file_download->ses != ses)
203 continue;
205 file_download->ses = s;
208 return;
211 foreachsafe (file_download, next, downloads) {
212 if (file_download->ses != ses)
213 continue;
215 if (!file_download->external_handler) {
216 file_download->ses = NULL;
217 continue;
220 abort_download(file_download);
225 static void
226 download_error_dialog(struct file_download *file_download, int saved_errno)
228 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
229 struct session *ses = file_download->ses;
230 struct terminal *term = file_download->term;
232 if (!ses) return;
234 info_box(term, MSGBOX_FREE_TEXT,
235 N_("Download error"), ALIGN_CENTER,
236 msg_text(term, N_("Could not create file '%s':\n%s"),
237 file_download->file, emsg));
240 static int
241 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
243 struct fragment *frag;
245 if (file_download->download.progress && file_download->download.progress->seek) {
246 file_download->seek = file_download->download.progress->seek;
247 file_download->download.progress->seek = 0;
248 /* This is exclusive with the prealloc, thus we can perform
249 * this in front of that thing safely. */
250 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
251 download_error_dialog(file_download, errno);
252 return 0;
256 foreach (frag, cached->frag) {
257 off_t remain = file_download->seek - frag->offset;
258 int *h = &file_download->handle;
259 ssize_t w;
261 if (remain < 0 || frag->length <= remain)
262 continue;
264 #ifdef USE_OPEN_PREALLOC
265 if (!file_download->seek
266 && (!file_download->download.progress
267 || file_download->download.progress->size > 0)) {
268 close(*h);
269 *h = open_prealloc(file_download->file,
270 O_CREAT|O_WRONLY|O_TRUNC,
271 0666,
272 file_download->download.progress
273 ? file_download->download.progress->size
274 : cached->length);
275 if (*h == -1) {
276 download_error_dialog(file_download, errno);
277 return 0;
279 set_bin(*h);
281 #endif
283 w = safe_write(*h, frag->data + remain, frag->length - remain);
284 if (w == -1) {
285 download_error_dialog(file_download, errno);
286 return 0;
289 file_download->seek += w;
292 return 1;
295 static void
296 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
298 if (term && get_opt_int("document.download.notify_bell",
299 file_download->ses)
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, sizeof(buf), "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 } else {
376 exec_on_terminal(term, file_download->external_handler,
377 file_download->file,
378 file_download->block ? TERM_EXEC_FG : TERM_EXEC_BG);
380 file_download->delete = 0;
381 abort_download_and_beep(file_download, term);
382 return;
385 if (file_download->notify) {
386 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
388 /* This is apparently a little racy. Deleting the box item will
389 * update the download browser _after_ the notification dialog
390 * has been drawn whereby it will be hidden. This should make
391 * the download browser update before launcing any
392 * notification. */
393 done_download_display(file_download);
395 if (url) {
396 info_box(term, MSGBOX_FREE_TEXT,
397 N_("Download"), ALIGN_CENTER,
398 msg_text(term, N_("Download complete:\n%s"), url));
399 mem_free(url);
403 if (file_download->remotetime
404 && get_opt_bool("document.download.set_original_time",
405 file_download->ses)) {
406 struct utimbuf foo;
408 foo.actime = foo.modtime = file_download->remotetime;
409 utime(file_download->file, &foo);
412 abort_download_and_beep(file_download, term);
415 static void
416 download_data(struct download *download, struct file_download *file_download)
418 struct cache_entry *cached = download->cached;
420 if (!cached || is_in_queued_state(download->state)) {
421 download_data_store(download, file_download);
422 return;
425 if (cached->last_modified)
426 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
428 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
429 cancel_download(&file_download->download, 0);
431 assertm(compare_uri(cached->uri, file_download->uri, 0),
432 "Redirecting using bad base URI");
434 done_uri(file_download->uri);
436 file_download->uri = get_uri_reference(cached->redirect);
437 file_download->download.state = S_WAIT_REDIR;
439 if (file_download->dlg_data)
440 redraw_dialog(file_download->dlg_data, 1);
442 load_uri(file_download->uri, cached->uri, &file_download->download,
443 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
444 download->progress ? download->progress->start : 0);
446 return;
449 if (!write_cache_entry_to_file(cached, file_download)) {
450 detach_connection(download, file_download->seek);
451 abort_download(file_download);
452 return;
455 detach_connection(download, file_download->seek);
456 download_data_store(download, file_download);
460 /* XXX: We assume that resume is everytime zero in lun's callbacks. */
461 struct lun_hop {
462 struct terminal *term;
463 unsigned char *ofile, *file;
465 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T);
466 void *data;
469 enum {
470 COMMON_DOWNLOAD_DO = 0,
471 CONTINUE_DOWNLOAD_DO
474 struct cmdw_hop {
475 int magic; /* Must be first --witekfl */
476 struct session *ses;
477 unsigned char *real_file;
480 struct codw_hop {
481 int magic; /* must be first --witekfl */
482 struct type_query *type_query;
483 unsigned char *real_file;
484 unsigned char *file;
487 struct cdf_hop {
488 unsigned char **real_file;
489 void (*callback)(struct terminal *, int, void *, download_flags_T);
490 void *data;
493 static void
494 lun_alternate(void *lun_hop_)
496 struct lun_hop *lun_hop = lun_hop_;
498 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, DOWNLOAD_START);
499 mem_free_if(lun_hop->ofile);
500 mem_free(lun_hop);
503 static void
504 lun_cancel(void *lun_hop_)
506 struct lun_hop *lun_hop = lun_hop_;
508 lun_hop->callback(lun_hop->term, NULL, lun_hop->data, DOWNLOAD_START);
509 mem_free_if(lun_hop->ofile);
510 mem_free_if(lun_hop->file);
511 mem_free(lun_hop);
514 static void
515 lun_overwrite(void *lun_hop_)
517 struct lun_hop *lun_hop = lun_hop_;
519 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0);
520 mem_free_if(lun_hop->file);
521 mem_free(lun_hop);
524 static void common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags);
526 static void
527 lun_resume(void *lun_hop_)
529 struct lun_hop *lun_hop = lun_hop_;
530 struct cdf_hop *cdf_hop = lun_hop->data;
532 int magic = *(int *)cdf_hop->data;
534 if (magic == CONTINUE_DOWNLOAD_DO) {
535 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
537 if (!cmdw_hop) {
538 lun_cancel(lun_hop);
539 return;
540 } else {
541 struct codw_hop *codw_hop = cdf_hop->data;
542 struct type_query *type_query = codw_hop->type_query;
544 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
545 cmdw_hop->ses = type_query->ses;
546 /* FIXME: Current ses->download_uri is overwritten here --witekfl */
547 cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri);
549 if (type_query->external_handler) mem_free_if(codw_hop->file);
550 tp_cancel(type_query);
551 mem_free(codw_hop);
553 cdf_hop->real_file = &cmdw_hop->real_file;
554 cdf_hop->data = cmdw_hop;
555 cdf_hop->callback = common_download_do;
558 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, DOWNLOAD_RESUME);
559 mem_free_if(lun_hop->file);
560 mem_free(lun_hop);
564 static void
565 lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T flags,
566 void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T flags),
567 void *data)
569 /* [gettext_accelerator_context(.lookup_unique_name)] */
570 struct lun_hop *lun_hop;
571 unsigned char *file;
572 int overwrite;
574 ofile = expand_tilde(ofile);
576 /* Minor code duplication to prevent useless call to get_opt_int()
577 * if possible. --Zas */
578 if (flags & DOWNLOAD_RESUME) {
579 callback(term, ofile, data, flags);
580 return;
583 /* !overwrite means always silently overwrite, which may be admitelly
584 * indeed a little confusing ;-) */
585 overwrite = get_opt_int("document.download.overwrite", NULL);
586 if (!overwrite) {
587 /* Nothing special to do... */
588 callback(term, ofile, data, flags);
589 return;
592 /* Check if file is a directory, and use a default name if it's the
593 * case. */
594 if (file_is_dir(ofile)) {
595 info_box(term, MSGBOX_FREE_TEXT,
596 N_("Download error"), ALIGN_CENTER,
597 msg_text(term, N_("'%s' is a directory."),
598 ofile));
599 mem_free(ofile);
600 callback(term, NULL, data, flags);
601 return;
604 /* Check if the file already exists (file != ofile). */
605 file = get_unique_name(ofile);
607 if (!file || overwrite == 1 || file == ofile) {
608 /* Still nothing special to do... */
609 if (file != ofile) mem_free(ofile);
610 callback(term, file, data, flags);
611 return;
614 /* overwrite == 2 (ask) and file != ofile (=> original file already
615 * exists) */
617 lun_hop = mem_calloc(1, sizeof(*lun_hop));
618 if (!lun_hop) {
619 if (file != ofile) mem_free(file);
620 mem_free(ofile);
621 callback(term, NULL, data, flags);
622 return;
624 lun_hop->term = term;
625 lun_hop->ofile = ofile;
626 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
627 lun_hop->callback = callback;
628 lun_hop->data = data;
630 msg_box(term, NULL, MSGBOX_FREE_TEXT,
631 N_("File exists"), ALIGN_CENTER,
632 msg_text(term, N_("This file already exists:\n"
633 "%s\n\n"
634 "The alternative filename is:\n"
635 "%s"),
636 empty_string_or_(lun_hop->ofile),
637 empty_string_or_(file)),
638 lun_hop, 4,
639 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
640 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
641 MSG_BOX_BUTTON(N_("~Resume download of the original file"), lun_resume, 0),
642 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
647 static void
648 create_download_file_do(struct terminal *term, unsigned char *file, void *data,
649 download_flags_T flags)
651 struct cdf_hop *cdf_hop = data;
652 unsigned char *wd;
653 int h = -1;
654 int saved_errno;
655 #ifdef NO_FILE_SECURITY
656 int sf = 0;
657 #else
658 int sf = flags & DOWNLOAD_EXTERNAL;
659 #endif
661 if (!file) goto finish;
663 wd = get_cwd();
664 set_cwd(term->cwd);
666 /* Create parent directories if needed. */
667 mkalldirs(file);
669 /* O_APPEND means repositioning at the end of file before each write(),
670 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
671 * --pasky */
672 h = open(file, O_CREAT | O_WRONLY | (flags & DOWNLOAD_RESUME ? 0 : O_TRUNC)
673 | (sf && !(flags & DOWNLOAD_RESUME) ? O_EXCL : 0),
674 sf ? 0600 : 0666);
675 saved_errno = errno; /* Saved in case of ... --Zas */
677 if (wd) {
678 set_cwd(wd);
679 mem_free(wd);
682 if (h == -1) {
683 info_box(term, MSGBOX_FREE_TEXT,
684 N_("Download error"), ALIGN_CENTER,
685 msg_text(term, N_("Could not create file '%s':\n%s"),
686 file, strerror(saved_errno)));
688 mem_free(file);
689 goto finish;
691 } else {
692 set_bin(h);
694 if (!(flags & DOWNLOAD_EXTERNAL)) {
695 unsigned char *download_dir = get_opt_str("document.download.directory", NULL);
696 int i;
698 safe_strncpy(download_dir, file, MAX_STR_LEN);
700 /* Find the used directory so it's available in history */
701 for (i = strlen(download_dir); i >= 0; i--)
702 if (dir_sep(download_dir[i]))
703 break;
704 download_dir[i + 1] = 0;
708 if (cdf_hop->real_file)
709 *cdf_hop->real_file = file;
710 else
711 mem_free(file);
713 finish:
714 cdf_hop->callback(term, h, cdf_hop->data, flags);
715 mem_free(cdf_hop);
718 void
719 create_download_file(struct terminal *term, unsigned char *fi,
720 unsigned char **real_file, download_flags_T flags,
721 void (*callback)(struct terminal *, int, void *, download_flags_T),
722 void *data)
724 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
725 unsigned char *wd;
727 if (!cdf_hop) {
728 callback(term, -1, data, 0);
729 return;
732 cdf_hop->real_file = real_file;
733 cdf_hop->callback = callback;
734 cdf_hop->data = data;
736 /* FIXME: The wd bussiness is probably useless here? --pasky */
737 wd = get_cwd();
738 set_cwd(term->cwd);
740 /* Also the tilde will be expanded here. */
741 lookup_unique_name(term, fi, flags, create_download_file_do, cdf_hop);
743 if (wd) {
744 set_cwd(wd);
745 mem_free(wd);
750 static unsigned char *
751 get_temp_name(struct uri *uri)
753 struct string name;
754 unsigned char *extension;
755 /* FIXME
756 * We use tempnam() here, which is unsafe (race condition), for now.
757 * This should be changed at some time, but it needs an in-depth work
758 * of whole download code. --Zas */
759 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
761 if (!nm) return NULL;
763 if (!init_string(&name)) {
764 free(nm);
765 return NULL;
768 add_to_string(&name, nm);
769 free(nm);
771 extension = get_extension_from_uri(uri);
772 if (extension) {
773 add_shell_safe_to_string(&name, extension, strlen(extension));
774 mem_free(extension);
777 return name.source;
781 static unsigned char *
782 subst_file(unsigned char *prog, unsigned char *file)
784 struct string name;
785 /* When there is no %s in the mailcap entry, the handler program reads
786 * data from stdin instead of a file. */
787 int input = 1;
789 if (!init_string(&name)) return NULL;
791 while (*prog) {
792 int p;
794 for (p = 0; prog[p] && prog[p] != '%'; p++);
796 add_bytes_to_string(&name, prog, p);
797 prog += p;
799 if (*prog == '%') {
800 input = 0;
801 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
802 #ifdef MAX_PATH
803 unsigned char new_path[MAX_PATH];
804 #else
805 unsigned char new_path[1024];
806 #endif
808 cygwin_conv_to_full_win32_path(file, new_path);
809 add_to_string(&name, new_path);
810 #else
811 add_shell_quoted_to_string(&name, file, strlen(file));
812 #endif
813 prog++;
817 if (input) {
818 struct string s;
820 if (init_string(&s)) {
821 add_to_string(&s, "/bin/cat ");
822 add_shell_quoted_to_string(&s, file, strlen(file));
823 add_to_string(&s, " | ");
824 add_string_to_string(&s, &name);
825 done_string(&name);
826 return s.source;
829 return name.source;
834 static void
835 common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
837 struct file_download *file_download;
838 struct cmdw_hop *cmdw_hop = data;
839 unsigned char *file = cmdw_hop->real_file;
840 struct session *ses = cmdw_hop->ses;
841 struct stat buf;
843 mem_free(cmdw_hop);
845 if (!file || fstat(fd, &buf)) return;
847 file_download = init_file_download(ses->download_uri, ses, file, fd);
848 if (!file_download) return;
850 if (flags & DOWNLOAD_RESUME) file_download->seek = buf.st_size;
852 display_download(ses->tab->term, file_download, ses);
854 load_uri(file_download->uri, ses->referrer, &file_download->download,
855 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
858 static void
859 common_download(struct session *ses, unsigned char *file, download_flags_T flags)
861 struct cmdw_hop *cmdw_hop;
863 if (!ses->download_uri) return;
865 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
866 if (!cmdw_hop) return;
867 cmdw_hop->ses = ses;
868 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
870 kill_downloads_to_file(file);
872 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, flags,
873 common_download_do, cmdw_hop);
876 void
877 start_download(void *ses, unsigned char *file)
879 common_download(ses, file, DOWNLOAD_START);
882 void
883 resume_download(void *ses, unsigned char *file)
885 common_download(ses, file, DOWNLOAD_RESUME);
890 static void
891 continue_download_do(struct terminal *term, int fd, void *data, download_flags_T flags)
893 struct codw_hop *codw_hop = data;
894 struct file_download *file_download = NULL;
895 struct type_query *type_query;
897 assert(codw_hop);
898 assert(codw_hop->type_query);
899 assert(codw_hop->type_query->uri);
900 assert(codw_hop->type_query->ses);
902 type_query = codw_hop->type_query;
903 if (!codw_hop->real_file) goto cancel;
905 file_download = init_file_download(type_query->uri, type_query->ses,
906 codw_hop->real_file, fd);
907 if (!file_download) goto cancel;
909 if (type_query->external_handler) {
910 file_download->external_handler = subst_file(type_query->external_handler,
911 codw_hop->file);
912 file_download->delete = 1;
913 file_download->copiousoutput = type_query->copiousoutput;
914 mem_free(codw_hop->file);
915 mem_free_set(&type_query->external_handler, NULL);
918 file_download->block = !!type_query->block;
920 /* Done here and not in init_file_download() so that the external
921 * handler can become initialized. */
922 display_download(term, file_download, type_query->ses);
924 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
925 done_type_query(type_query);
927 mem_free(codw_hop);
928 return;
930 cancel:
931 if (type_query->external_handler) mem_free_if(codw_hop->file);
932 tp_cancel(type_query);
933 mem_free(codw_hop);
936 static void
937 continue_download(void *data, unsigned char *file)
939 struct type_query *type_query = data;
940 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
942 if (!codw_hop) {
943 tp_cancel(type_query);
944 return;
947 if (type_query->external_handler) {
948 /* FIXME: get_temp_name() calls tempnam(). --Zas */
949 file = get_temp_name(type_query->uri);
950 if (!file) {
951 mem_free(codw_hop);
952 tp_cancel(type_query);
953 return;
957 codw_hop->type_query = type_query;
958 codw_hop->file = file;
959 codw_hop->magic = CONTINUE_DOWNLOAD_DO;
961 kill_downloads_to_file(file);
963 create_download_file(type_query->ses->tab->term, file,
964 &codw_hop->real_file,
965 type_query->external_handler
966 ? DOWNLOAD_START | DOWNLOAD_EXTERNAL
967 : DOWNLOAD_START,
968 continue_download_do, codw_hop);
972 static struct type_query *
973 init_type_query(struct session *ses, struct download *download,
974 struct cache_entry *cached)
976 struct type_query *type_query;
978 /* There can be only one ... */
979 foreach (type_query, ses->type_queries)
980 if (compare_uri(type_query->uri, ses->loading_uri, 0))
981 return NULL;
983 type_query = mem_calloc(1, sizeof(*type_query));
984 if (!type_query) return NULL;
986 type_query->uri = get_uri_reference(ses->loading_uri);
987 type_query->ses = ses;
988 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
990 type_query->cached = cached;
991 type_query->cgi = cached->cgi;
992 object_lock(type_query->cached);
994 move_download(download, &type_query->download, PRI_MAIN);
995 download->state = S_OK;
997 add_to_list(ses->type_queries, type_query);
999 return type_query;
1002 void
1003 done_type_query(struct type_query *type_query)
1005 /* Unregister any active download */
1006 cancel_download(&type_query->download, 0);
1008 object_unlock(type_query->cached);
1009 done_uri(type_query->uri);
1010 mem_free_if(type_query->external_handler);
1011 mem_free_if(type_query->target_frame);
1012 del_from_list(type_query);
1013 mem_free(type_query);
1017 void
1018 tp_cancel(void *data)
1020 struct type_query *type_query = data;
1022 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
1023 cancel_download(&type_query->download, 1);
1024 done_type_query(type_query);
1028 void
1029 tp_save(struct type_query *type_query)
1031 mem_free_set(&type_query->external_handler, NULL);
1032 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1035 /** This button handler uses the add_dlg_button() interface so that pressing
1036 * 'Show header' will not close the type query dialog. */
1037 static widget_handler_status_T
1038 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1040 struct type_query *type_query = widget_data->widget->data;
1042 cached_header_dialog(type_query->ses, type_query->cached);
1044 return EVENT_PROCESSED;
1048 /** @bug FIXME: We need to modify this function to take frame data
1049 * instead, as we want to use this function for frames as well (now,
1050 * when frame has content type text/plain, it is ignored and displayed
1051 * as HTML). */
1052 void
1053 tp_display(struct type_query *type_query)
1055 struct view_state *vs;
1056 struct session *ses = type_query->ses;
1057 struct uri *loading_uri = ses->loading_uri;
1058 unsigned char *target_frame = ses->task.target.frame;
1060 ses->loading_uri = type_query->uri;
1061 ses->task.target.frame = type_query->target_frame;
1062 vs = ses_forward(ses, /* type_query->frame */ 0);
1063 if (vs) vs->plain = 1;
1064 ses->loading_uri = loading_uri;
1065 ses->task.target.frame = target_frame;
1067 if (/* !type_query->frame */ 1) {
1068 struct download *old = &type_query->download;
1069 struct download *new = &cur_loc(ses)->download;
1071 new->callback = (download_callback_T *) doc_loading_callback;
1072 new->data = ses;
1074 move_download(old, new, PRI_MAIN);
1077 display_timer(ses);
1078 done_type_query(type_query);
1082 static void
1083 tp_open(struct type_query *type_query)
1085 if (!type_query->external_handler || !*type_query->external_handler) {
1086 tp_display(type_query);
1087 return;
1090 if (type_query->uri->protocol == PROTOCOL_FILE && !type_query->cgi) {
1091 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1092 unsigned char *handler = NULL;
1094 if (file) {
1095 decode_uri(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;