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