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