Add @relates markup for Doxygen
[elinks/elinks-j605.git] / src / session / download.c
bloba3ac779dda765c08d2f6866181c1e5aa48b2fc1a
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 "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);
67 int
68 download_is_progressing(struct download *download)
70 return download
71 && is_in_state(download->state, S_TRANS)
72 && has_progress(download->progress);
75 int
76 are_there_downloads(void)
78 struct file_download *file_download;
80 foreach (file_download, downloads)
81 if (!file_download->external_handler)
82 return 1;
84 return 0;
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);
104 return NULL;
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;
126 void
127 abort_download(struct file_download *file_download)
129 #if 0
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));
135 #endif
137 done_download_display(file_download);
139 if (file_download->ses)
140 check_questions_queue(file_download->ses);
142 if (file_download->dlg_data)
143 cancel_dialog(file_download->dlg_data, NULL);
144 cancel_download(&file_download->download, file_download->stop);
145 if (file_download->uri) done_uri(file_download->uri);
147 if (file_download->handle != -1) {
148 prealloc_truncate(file_download->handle, file_download->seek);
149 close(file_download->handle);
152 mem_free_if(file_download->external_handler);
153 if (file_download->file) {
154 if (file_download->delete) unlink(file_download->file);
155 mem_free(file_download->file);
157 del_from_list(file_download);
158 mem_free(file_download);
162 static void
163 kill_downloads_to_file(unsigned char *file)
165 struct file_download *file_download;
167 foreach (file_download, downloads) {
168 if (strcmp(file_download->file, file))
169 continue;
171 file_download = file_download->prev;
172 abort_download(file_download->next);
177 void
178 abort_all_downloads(void)
180 while (!list_empty(downloads))
181 abort_download(downloads.next);
185 void
186 destroy_downloads(struct session *ses)
188 struct file_download *file_download, *next;
189 struct session *s;
191 /* We are supposed to blat all downloads to external handlers belonging
192 * to @ses, but we will refuse to do so if there is another session
193 * bound to this terminal. That looks like the reasonable thing to do,
194 * fulfilling the principle of least astonishment. */
195 foreach (s, sessions) {
196 if (s == ses || s->tab->term != ses->tab->term)
197 continue;
199 foreach (file_download, downloads) {
200 if (file_download->ses != ses)
201 continue;
203 file_download->ses = s;
206 return;
209 foreachsafe (file_download, next, downloads) {
210 if (file_download->ses != ses)
211 continue;
213 if (!file_download->external_handler) {
214 file_download->ses = NULL;
215 continue;
218 abort_download(file_download);
222 void
223 detach_downloads_from_terminal(struct terminal *term)
225 struct file_download *file_download, *next;
227 assert(term != NULL);
228 if_assert_failed return;
230 foreachsafe (file_download, next, downloads) {
231 if (file_download->term != term)
232 continue;
234 if (!file_download->external_handler) {
235 file_download->term = NULL;
236 if (file_download->ses
237 && file_download->ses->tab->term == term)
238 file_download->ses = NULL;
239 continue;
242 abort_download(file_download);
246 static void
247 download_error_dialog(struct file_download *file_download, int saved_errno)
249 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
250 struct session *ses = file_download->ses;
251 struct terminal *term = file_download->term;
253 if (!ses) return;
255 info_box(term, MSGBOX_FREE_TEXT,
256 N_("Download error"), ALIGN_CENTER,
257 msg_text(term, N_("Could not create file '%s':\n%s"),
258 file_download->file, emsg));
261 static int
262 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
264 struct fragment *frag;
266 if (file_download->download.progress && file_download->download.progress->seek) {
267 file_download->seek = file_download->download.progress->seek;
268 file_download->download.progress->seek = 0;
269 /* This is exclusive with the prealloc, thus we can perform
270 * this in front of that thing safely. */
271 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
272 download_error_dialog(file_download, errno);
273 return 0;
277 foreach (frag, cached->frag) {
278 off_t remain = file_download->seek - frag->offset;
279 int *h = &file_download->handle;
280 ssize_t w;
282 if (remain < 0 || frag->length <= remain)
283 continue;
285 #ifdef USE_OPEN_PREALLOC
286 if (!file_download->seek
287 && (!file_download->download.progress
288 || file_download->download.progress->size > 0)) {
289 close(*h);
290 *h = open_prealloc(file_download->file,
291 O_CREAT|O_WRONLY|O_TRUNC,
292 0666,
293 file_download->download.progress
294 ? file_download->download.progress->size
295 : cached->length);
296 if (*h == -1) {
297 download_error_dialog(file_download, errno);
298 return 0;
300 set_bin(*h);
302 #endif
304 w = safe_write(*h, frag->data + remain, frag->length - remain);
305 if (w == -1) {
306 download_error_dialog(file_download, errno);
307 return 0;
310 file_download->seek += w;
313 return 1;
316 static void
317 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
319 if (term && get_opt_int("document.download.notify_bell")
320 + file_download->notify >= 2) {
321 beep_terminal(term);
324 abort_download(file_download);
327 static void
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 (!term) {
336 /* No term here, so no beep. --Zas */
337 abort_download(file_download);
338 return;
341 if (is_in_progress_state(download->state)) {
342 if (file_download->dlg_data)
343 redraw_dialog(file_download->dlg_data, 1);
344 return;
347 if (!is_in_state(download->state, S_OK)) {
348 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
349 struct connection_state state = download->state;
351 abort_download_and_beep(file_download, term);
353 if (!url) return;
355 info_box(term, MSGBOX_FREE_TEXT,
356 N_("Download error"), ALIGN_CENTER,
357 msg_text(term, N_("Error downloading %s:\n\n%s"),
358 url, get_state_message(state, term)));
359 mem_free(url);
360 return;
363 if (file_download->external_handler) {
364 prealloc_truncate(file_download->handle, file_download->seek);
365 close(file_download->handle);
366 file_download->handle = -1;
367 exec_on_terminal(term, file_download->external_handler,
368 file_download->file,
369 file_download->block ? TERM_EXEC_FG : TERM_EXEC_BG);
370 file_download->delete = 0;
371 abort_download_and_beep(file_download, term);
372 return;
375 if (file_download->notify) {
376 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
378 /* This is apparently a little racy. Deleting the box item will
379 * update the download browser _after_ the notification dialog
380 * has been drawn whereby it will be hidden. This should make
381 * the download browser update before launcing any
382 * notification. */
383 done_download_display(file_download);
385 if (url) {
386 info_box(term, MSGBOX_FREE_TEXT,
387 N_("Download"), ALIGN_CENTER,
388 msg_text(term, N_("Download complete:\n%s"), url));
389 mem_free(url);
393 if (file_download->remotetime
394 && get_opt_bool("document.download.set_original_time")) {
395 struct utimbuf foo;
397 foo.actime = foo.modtime = file_download->remotetime;
398 utime(file_download->file, &foo);
401 abort_download_and_beep(file_download, term);
404 static void
405 download_data(struct download *download, struct file_download *file_download)
407 struct cache_entry *cached = download->cached;
409 if (!cached || is_in_queued_state(download->state)) {
410 download_data_store(download, file_download);
411 return;
414 if (cached->last_modified)
415 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
417 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
418 cancel_download(&file_download->download, 0);
420 assertm(compare_uri(cached->uri, file_download->uri, 0),
421 "Redirecting using bad base URI");
423 done_uri(file_download->uri);
425 file_download->uri = get_uri_reference(cached->redirect);
426 file_download->download.state = connection_state(S_WAIT_REDIR);
428 if (file_download->dlg_data)
429 redraw_dialog(file_download->dlg_data, 1);
431 load_uri(file_download->uri, cached->uri, &file_download->download,
432 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
433 download->progress ? download->progress->start : 0);
435 return;
438 if (!write_cache_entry_to_file(cached, file_download)) {
439 detach_connection(download, file_download->seek);
440 abort_download(file_download);
441 return;
444 detach_connection(download, file_download->seek);
445 download_data_store(download, file_download);
449 /** The user is being asked what to do when the local file for
450 * the download already exists. This structure is allocated by
451 * lookup_unique_name() and freed by each lun_* function:
452 * lun_alternate(), lun_cancel(), lun_overwrite(), and lun_resume(). */
453 struct lun_hop {
454 /** The terminal in which ELinks is asking the question.
455 * This gets passed to the callback. */
456 struct terminal *term;
458 /** The name of the local file into which the data was
459 * originally going to be downloaded, but which already
460 * exists. In this string, "~" has already been expanded
461 * to the home directory. The string must be freed with
462 * mem_free(). */
463 unsigned char *ofile;
465 /** An alternative file name that the user may choose instead
466 * of #ofile. The string must be freed with mem_free(). */
467 unsigned char *file;
469 /** This function will be called when the user answers.
470 * See lookup_unique_name() for more information. */
471 void (*callback)(struct terminal *term, unsigned char *file,
472 void *data, enum download_resume resume);
474 /** A pointer to be passed to #callback. If #resume includes
475 * ::DOWNLOAD_RESUME_ALLOWED, this must point to struct
476 * cdf_hop because the pointer can be read by lun_resume(),
477 * which assumes so. */
478 void *data;
480 /** Whether the download can be resumed.
481 * The ::DOWNLOAD_RESUME_SELECTED bit should be clear
482 * because otherwise there would have been no reason to
483 * ask the user and initialize this structure. */
484 enum download_resume resume;
487 /** To let lun_resume() detect whether cdf_hop.data points to struct
488 * cmdw_hop or to struct codw_hop, each of those structures begins
489 * with <code>int magic</code>, whose value is one of these
490 * constants. */
491 enum {
492 COMMON_DOWNLOAD_DO = 0,
493 CONTINUE_DOWNLOAD_DO
496 /** Data saved by common_download() for the common_download_do()
497 * callback. */
498 struct cmdw_hop {
499 /** Always ::COMMON_DOWNLOAD_DO. */
500 int magic; /* Must be first --witekfl */
502 struct session *ses;
504 /** The name of the local file to which the data will be
505 * downloaded. This is initially NULL, but its address is
506 * given to create_download_file(), which arranges for the
507 * pointer to be set before common_download_do() is called.
508 * The string must be freed with mem_free(). */
509 unsigned char *real_file;
512 /** Data saved by continue_download() for the continue_download_do()
513 * callback. */
514 struct codw_hop {
515 /** Always ::CONTINUE_DOWNLOAD_DO. */
516 int magic; /* must be first --witekfl */
518 struct type_query *type_query;
520 /** The name of the local file to which the data will be
521 * downloaded. This is initially NULL, but its address is
522 * given to create_download_file(), which arranges for the
523 * pointer to be set before continue_download_do() is called.
524 * The string must be freed with mem_free(). */
525 unsigned char *real_file;
527 unsigned char *file;
530 /** Data saved by create_download_file() for the create_download_file_do()
531 * callback. */
532 struct cdf_hop {
533 /** Where to save the name of the file that was actually
534 * opened. One of the arguments of #callback is a file
535 * descriptor for this file. @c real_file can be NULL if
536 * #callback does not care about the name. */
537 unsigned char **real_file;
539 /** If nonzero, give only the user herself access to the file
540 * (even if the umask is looser), and create the file with
541 * @c O_EXCL unless resuming. */
542 int safe;
544 /** This function will be called when the file has been opened,
545 * or when it is known that the file will not be opened.
546 * See create_download_file() for more information. */
547 void (*callback)(struct terminal *, int, void *, enum download_resume);
549 /** A pointer to be passed to #callback. If the @a resume
550 * argument given to create_download_file() included
551 * ::DOWNLOAD_RESUME_ALLOWED, this must point to struct
552 * cmdw_hop or struct codw_hop because the pointer can be
553 * read by lun_resume(), which assumes so. */
554 void *data;
557 static void
558 lun_alternate(void *lun_hop_)
560 struct lun_hop *lun_hop = lun_hop_;
562 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data,
563 lun_hop->resume);
564 mem_free_if(lun_hop->ofile);
565 mem_free(lun_hop);
568 static void
569 lun_cancel(void *lun_hop_)
571 struct lun_hop *lun_hop = lun_hop_;
573 lun_hop->callback(lun_hop->term, NULL, lun_hop->data,
574 lun_hop->resume);
575 mem_free_if(lun_hop->ofile);
576 mem_free_if(lun_hop->file);
577 mem_free(lun_hop);
580 static void
581 lun_overwrite(void *lun_hop_)
583 struct lun_hop *lun_hop = lun_hop_;
585 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data,
586 lun_hop->resume);
587 mem_free_if(lun_hop->file);
588 mem_free(lun_hop);
591 static void common_download_do(struct terminal *term, int fd, void *data,
592 enum download_resume resume);
594 /*! @relates lun_hop */
595 static void
596 lun_resume(void *lun_hop_)
598 struct lun_hop *lun_hop = lun_hop_;
599 struct cdf_hop *cdf_hop = lun_hop->data;
601 int magic = *(int *)cdf_hop->data;
603 if (magic == CONTINUE_DOWNLOAD_DO) {
604 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
606 if (!cmdw_hop) {
607 lun_cancel(lun_hop);
608 return;
609 } else {
610 struct codw_hop *codw_hop = cdf_hop->data;
611 struct type_query *type_query = codw_hop->type_query;
613 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
614 cmdw_hop->ses = type_query->ses;
615 /* FIXME: Current ses->download_uri is overwritten here --witekfl */
616 cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri);
618 if (type_query->external_handler) mem_free_if(codw_hop->file);
619 tp_cancel(type_query);
620 mem_free(codw_hop);
622 cdf_hop->real_file = &cmdw_hop->real_file;
623 cdf_hop->data = cmdw_hop;
624 cdf_hop->callback = common_download_do;
627 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data,
628 lun_hop->resume | DOWNLOAD_RESUME_SELECTED);
629 mem_free_if(lun_hop->file);
630 mem_free(lun_hop);
634 /** If attempting to download to an existing file, perhaps ask
635 * the user whether to resume, overwrite, or save elsewhere.
636 * This function constructs a struct lun_hop, which will be freed
637 * when the user answers the question.
639 * @param term
640 * The terminal in which this function should show its UI.
642 * @param[in] ofile
643 * A proposed name for the local file to which the data would be
644 * downloaded. "~" here refers to the home directory.
645 * lookup_unique_name() treats this original string as read-only.
647 * @param[in] resume
648 * Indicates if the user already chose to resume downloading,
649 * before ELinks even asked for the file name.
650 * See ::ACT_MAIN_LINK_DOWNLOAD_RESUME.
652 * @param callback
653 * Will be called when the user answers, or right away if the question
654 * need not or cannot be asked. In the parameters of the callback,
655 * @a term and @a data get their values directly from the arguments of
656 * lookup_unique_name(). @a file is the name of the local file to
657 * which the data should be downloaded, or NULL if the download should
658 * not begin. The callback is responsible of doing mem_free(@a file).
660 * @param data
661 * A pointer to be passed to @a callback. Although this is a void *,
662 * it must always point to struct cdf_hop because the pointer can get
663 * passed to lun_resume(), which assumes so.
665 * @relates lun_hop */
666 static void
667 lookup_unique_name(struct terminal *term, unsigned char *ofile,
668 enum download_resume resume,
669 void (*callback)(struct terminal *term, unsigned char *file,
670 void *data, enum download_resume resume),
671 void *data)
673 /* [gettext_accelerator_context(.lookup_unique_name)] */
674 struct lun_hop *lun_hop;
675 unsigned char *file;
676 int overwrite;
678 ofile = expand_tilde(ofile);
680 /* Minor code duplication to prevent useless call to get_opt_int()
681 * if possible. --Zas */
682 if (resume & DOWNLOAD_RESUME_SELECTED) {
683 callback(term, ofile, data, resume);
684 return;
687 /* !overwrite means always silently overwrite, which may be admitelly
688 * indeed a little confusing ;-) */
689 overwrite = get_opt_int("document.download.overwrite");
690 if (!overwrite) {
691 /* Nothing special to do... */
692 callback(term, ofile, data, resume);
693 return;
696 /* Check if file is a directory, and use a default name if it's the
697 * case. */
698 if (file_is_dir(ofile)) {
699 info_box(term, MSGBOX_FREE_TEXT,
700 N_("Download error"), ALIGN_CENTER,
701 msg_text(term, N_("'%s' is a directory."),
702 ofile));
703 mem_free(ofile);
704 callback(term, NULL, data, resume & ~DOWNLOAD_RESUME_SELECTED);
705 return;
708 /* Check if the file already exists (file != ofile). */
709 file = get_unique_name(ofile);
711 if (!file || overwrite == 1 || file == ofile) {
712 /* Still nothing special to do... */
713 if (file != ofile) mem_free(ofile);
714 callback(term, file, data, resume & ~DOWNLOAD_RESUME_SELECTED);
715 return;
718 /* overwrite == 2 (ask) and file != ofile (=> original file already
719 * exists) */
721 lun_hop = mem_calloc(1, sizeof(*lun_hop));
722 if (!lun_hop) {
723 if (file != ofile) mem_free(file);
724 mem_free(ofile);
725 callback(term, NULL, data, resume & ~DOWNLOAD_RESUME_SELECTED);
726 return;
728 lun_hop->term = term;
729 lun_hop->ofile = ofile;
730 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
731 lun_hop->callback = callback;
732 lun_hop->data = data;
733 lun_hop->resume = resume;
735 msg_box(term, NULL, MSGBOX_FREE_TEXT,
736 N_("File exists"), ALIGN_CENTER,
737 msg_text(term, N_("This file already exists:\n"
738 "%s\n\n"
739 "The alternative filename is:\n"
740 "%s"),
741 empty_string_or_(lun_hop->ofile),
742 empty_string_or_(file)),
743 lun_hop, 4,
744 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
745 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
746 MSG_BOX_BUTTON((resume & DOWNLOAD_RESUME_ALLOWED
747 ? N_("~Resume download of the original file")
748 : NULL),
749 lun_resume, 0),
750 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
755 /** Now that the final name of the download file has been chosen,
756 * open the file and call the callback that was originally given to
757 * create_download_file().
759 * create_download_file() passes this function as a callback to
760 * lookup_unique_name().
762 * @relates cdf_hop */
763 static void
764 create_download_file_do(struct terminal *term, unsigned char *file,
765 void *data, enum download_resume resume)
767 struct cdf_hop *cdf_hop = data;
768 unsigned char *wd;
769 int h = -1;
770 int saved_errno;
771 #ifdef NO_FILE_SECURITY
772 int sf = 0;
773 #else
774 int sf = cdf_hop->safe;
775 #endif
777 if (!file) goto finish;
779 wd = get_cwd();
780 set_cwd(term->cwd);
782 /* Create parent directories if needed. */
783 mkalldirs(file);
785 /* O_APPEND means repositioning at the end of file before each write(),
786 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
787 * --pasky */
788 h = open(file, O_CREAT | O_WRONLY
789 | (resume & DOWNLOAD_RESUME_SELECTED ? 0 : O_TRUNC)
790 | (sf && !(resume & DOWNLOAD_RESUME_SELECTED) ? O_EXCL : 0),
791 sf ? 0600 : 0666);
792 saved_errno = errno; /* Saved in case of ... --Zas */
794 if (wd) {
795 set_cwd(wd);
796 mem_free(wd);
799 if (h == -1) {
800 info_box(term, MSGBOX_FREE_TEXT,
801 N_("Download error"), ALIGN_CENTER,
802 msg_text(term, N_("Could not create file '%s':\n%s"),
803 file, strerror(saved_errno)));
805 mem_free(file);
806 goto finish;
808 } else {
809 set_bin(h);
811 if (!cdf_hop->safe) {
812 unsigned char *download_dir = get_opt_str("document.download.directory");
813 int i;
815 safe_strncpy(download_dir, file, MAX_STR_LEN);
817 /* Find the used directory so it's available in history */
818 for (i = strlen(download_dir); i >= 0; i--)
819 if (dir_sep(download_dir[i]))
820 break;
821 download_dir[i + 1] = 0;
825 if (cdf_hop->real_file)
826 *cdf_hop->real_file = file;
827 else
828 mem_free(file);
830 finish:
831 cdf_hop->callback(term, h, cdf_hop->data, resume);
832 mem_free(cdf_hop);
835 /** Create a file to which data can be downloaded.
836 * This function constructs a struct cdf_hop that will be freed
837 * when @a callback returns.
839 * @param term
840 * If any dialog boxes are needed, show them in this terminal.
842 * @param fi
843 * A proposed name for the local file to which the data would be
844 * downloaded. "~" here refers to the home directory.
845 * create_download_file() treats this original string as read-only.
847 * @param real_file
848 * If non-NULL, prepare to save in *@a real_file the name of the local
849 * file that was eventually opened. The callback must then free this
850 * string with mem_free().
852 * @param safe
853 * If nonzero, give only the user herself access to the file (even if
854 * the umask is looser), and create the file with @c O_EXCL unless
855 * resuming.
857 * @param resume
858 * Whether the download can be resumed, and whether the user already
859 * asked for it to be resumed.
861 * @param callback
862 * This function will be called when the file has been opened,
863 * or when it is known that the file will not be opened.
864 * In the parameters of the callback, @a term and @a data get their
865 * values directly from the arguments of create_download_file().
866 * @a fd is a file descriptor to the opened file, or -1 if the file
867 * will not be opened; the callback may read the name of this file
868 * from *@a real_file if @a real_file was not NULL.
869 * @a resume is the same as the @a resume argument of
870 * create_download_file(), except the ::DOWNLOAD_RESUME_SELECTED bit
871 * will be changed to match what the user chose.
873 * @param data
874 * A pointer to be passed to #callback. If the @a resume argument
875 * given to create_download_file() included ::DOWNLOAD_RESUME_ALLOWED,
876 * this must point to struct cmdw_hop or struct codw_hop because the
877 * pointer can be read by lun_resume(), which assumes so.
879 * @relates cdf_hop */
880 void
881 create_download_file(struct terminal *term, unsigned char *fi,
882 unsigned char **real_file, int safe,
883 enum download_resume resume,
884 void (*callback)(struct terminal *term, int fd,
885 void *data, enum download_resume resume),
886 void *data)
888 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
889 unsigned char *wd;
891 if (!cdf_hop) {
892 callback(term, -1, data, resume & ~DOWNLOAD_RESUME_SELECTED);
893 return;
896 cdf_hop->real_file = real_file;
897 cdf_hop->safe = safe;
898 cdf_hop->callback = callback;
899 cdf_hop->data = data;
901 /* FIXME: The wd bussiness is probably useless here? --pasky */
902 wd = get_cwd();
903 set_cwd(term->cwd);
905 /* Also the tilde will be expanded here. */
906 lookup_unique_name(term, fi, resume, create_download_file_do, cdf_hop);
908 if (wd) {
909 set_cwd(wd);
910 mem_free(wd);
915 static unsigned char *
916 get_temp_name(struct uri *uri)
918 struct string name;
919 unsigned char *extension;
920 /* FIXME
921 * We use tempnam() here, which is unsafe (race condition), for now.
922 * This should be changed at some time, but it needs an in-depth work
923 * of whole download code. --Zas */
924 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
926 if (!nm) return NULL;
928 if (!init_string(&name)) {
929 free(nm);
930 return NULL;
933 add_to_string(&name, nm);
934 free(nm);
936 extension = get_extension_from_uri(uri);
937 if (extension) {
938 add_shell_safe_to_string(&name, extension, strlen(extension));
939 mem_free(extension);
942 return name.source;
946 static unsigned char *
947 subst_file(unsigned char *prog, unsigned char *file)
949 struct string name;
950 /* When there is no %s in the mailcap entry, the handler program reads
951 * data from stdin instead of a file. */
952 int input = 1;
954 if (!init_string(&name)) return NULL;
956 while (*prog) {
957 int p;
959 for (p = 0; prog[p] && prog[p] != '%'; p++);
961 add_bytes_to_string(&name, prog, p);
962 prog += p;
964 if (*prog == '%') {
965 input = 0;
966 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
967 #ifdef MAX_PATH
968 unsigned char new_path[MAX_PATH];
969 #else
970 unsigned char new_path[1024];
971 #endif
973 cygwin_conv_to_full_win32_path(file, new_path);
974 add_to_string(&name, new_path);
975 #else
976 add_shell_quoted_to_string(&name, file, strlen(file));
977 #endif
978 prog++;
982 if (input) {
983 struct string s;
985 if (init_string(&s)) {
986 add_to_string(&s, "/bin/cat ");
987 add_shell_quoted_to_string(&s, file, strlen(file));
988 add_to_string(&s, " | ");
989 add_string_to_string(&s, &name);
990 done_string(&name);
991 return s.source;
994 return name.source;
999 /*! @relates cmdw_hop */
1000 static void
1001 common_download_do(struct terminal *term, int fd, void *data,
1002 enum download_resume resume)
1004 struct file_download *file_download;
1005 struct cmdw_hop *cmdw_hop = data;
1006 unsigned char *file = cmdw_hop->real_file;
1007 struct session *ses = cmdw_hop->ses;
1008 struct stat buf;
1010 mem_free(cmdw_hop);
1012 if (!file || fstat(fd, &buf)) return;
1014 file_download = init_file_download(ses->download_uri, ses, file, fd);
1015 if (!file_download) return;
1017 if (resume & DOWNLOAD_RESUME_SELECTED)
1018 file_download->seek = buf.st_size;
1020 display_download(ses->tab->term, file_download, ses);
1022 load_uri(file_download->uri, ses->referrer, &file_download->download,
1023 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
1026 /*! @relates cmdw_hop */
1027 static void
1028 common_download(struct session *ses, unsigned char *file,
1029 enum download_resume resume)
1031 struct cmdw_hop *cmdw_hop;
1033 if (!ses->download_uri) return;
1035 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
1036 if (!cmdw_hop) return;
1037 cmdw_hop->ses = ses;
1038 cmdw_hop->magic = COMMON_DOWNLOAD_DO;
1040 kill_downloads_to_file(file);
1042 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, 0,
1043 resume, common_download_do, cmdw_hop);
1046 /*! @relates cmdw_hop */
1047 void
1048 start_download(void *ses, unsigned char *file)
1050 common_download(ses, file,
1051 DOWNLOAD_RESUME_ALLOWED);
1055 /*! @relates cmdw_hop */
1056 void
1057 resume_download(void *ses, unsigned char *file)
1059 common_download(ses, file,
1060 DOWNLOAD_RESUME_ALLOWED | DOWNLOAD_RESUME_SELECTED);
1065 /*! @relates codw_hop */
1066 static void
1067 continue_download_do(struct terminal *term, int fd, void *data,
1068 enum download_resume resume)
1070 struct codw_hop *codw_hop = data;
1071 struct file_download *file_download = NULL;
1072 struct type_query *type_query;
1074 assert(codw_hop);
1075 assert(codw_hop->type_query);
1076 assert(codw_hop->type_query->uri);
1077 assert(codw_hop->type_query->ses);
1079 type_query = codw_hop->type_query;
1080 if (!codw_hop->real_file) goto cancel;
1082 file_download = init_file_download(type_query->uri, type_query->ses,
1083 codw_hop->real_file, fd);
1084 if (!file_download) goto cancel;
1086 if (type_query->external_handler) {
1087 file_download->external_handler = subst_file(type_query->external_handler,
1088 codw_hop->file);
1089 file_download->delete = 1;
1090 mem_free(codw_hop->file);
1091 mem_free_set(&type_query->external_handler, NULL);
1094 file_download->block = !!type_query->block;
1096 /* Done here and not in init_file_download() so that the external
1097 * handler can become initialized. */
1098 display_download(term, file_download, type_query->ses);
1100 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
1101 done_type_query(type_query);
1103 mem_free(codw_hop);
1104 return;
1106 cancel:
1107 if (type_query->external_handler) mem_free_if(codw_hop->file);
1108 tp_cancel(type_query);
1109 mem_free(codw_hop);
1112 /** When asked what to do with a file, the user chose to download it
1113 * to a local file named @a file.
1114 * Or an external handler was selected, in which case
1115 * type_query.external_handler is non-NULL and @a file does not
1116 * matter because this function will generate a name.
1118 * @relates codw_hop */
1119 static void
1120 continue_download(void *data, unsigned char *file)
1122 struct type_query *type_query = data;
1123 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
1125 if (!codw_hop) {
1126 tp_cancel(type_query);
1127 return;
1130 if (type_query->external_handler) {
1131 /* FIXME: get_temp_name() calls tempnam(). --Zas */
1132 file = get_temp_name(type_query->uri);
1133 if (!file) {
1134 mem_free(codw_hop);
1135 tp_cancel(type_query);
1136 return;
1140 codw_hop->type_query = type_query;
1141 codw_hop->file = file;
1142 codw_hop->magic = CONTINUE_DOWNLOAD_DO;
1144 kill_downloads_to_file(file);
1146 create_download_file(type_query->ses->tab->term, file,
1147 &codw_hop->real_file,
1148 !!type_query->external_handler,
1149 DOWNLOAD_RESUME_ALLOWED,
1150 continue_download_do, codw_hop);
1154 /*! @relates type_query */
1155 static struct type_query *
1156 init_type_query(struct session *ses, struct download *download,
1157 struct cache_entry *cached)
1159 struct type_query *type_query;
1161 /* There can be only one ... */
1162 foreach (type_query, ses->type_queries)
1163 if (compare_uri(type_query->uri, ses->loading_uri, 0))
1164 return NULL;
1166 type_query = mem_calloc(1, sizeof(*type_query));
1167 if (!type_query) return NULL;
1169 type_query->uri = get_uri_reference(ses->loading_uri);
1170 type_query->ses = ses;
1171 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
1173 type_query->cached = cached;
1174 type_query->cgi = cached->cgi;
1175 object_lock(type_query->cached);
1177 move_download(download, &type_query->download, PRI_MAIN);
1178 download->state = connection_state(S_OK);
1180 add_to_list(ses->type_queries, type_query);
1182 return type_query;
1185 /*! @relates type_query */
1186 void
1187 done_type_query(struct type_query *type_query)
1189 /* Unregister any active download */
1190 cancel_download(&type_query->download, 0);
1192 object_unlock(type_query->cached);
1193 done_uri(type_query->uri);
1194 mem_free_if(type_query->external_handler);
1195 mem_free_if(type_query->target_frame);
1196 del_from_list(type_query);
1197 mem_free(type_query);
1201 /*! @relates type_query */
1202 void
1203 tp_cancel(void *data)
1205 struct type_query *type_query = data;
1207 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
1208 cancel_download(&type_query->download, 1);
1209 done_type_query(type_query);
1213 /** The user chose "Save" when asked what to do with a file.
1214 * Now ask her where to save the file.
1216 * @relates type_query */
1217 void
1218 tp_save(struct type_query *type_query)
1220 mem_free_set(&type_query->external_handler, NULL);
1221 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1224 /** This button handler uses the add_dlg_button() interface so that pressing
1225 * 'Show header' will not close the type query dialog.
1227 * @relates type_query */
1228 static widget_handler_status_T
1229 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1231 struct type_query *type_query = widget_data->widget->data;
1233 cached_header_dialog(type_query->ses, type_query->cached);
1235 return EVENT_PROCESSED;
1239 /** @bug FIXME: We need to modify this function to take frame data
1240 * instead, as we want to use this function for frames as well (now,
1241 * when frame has content type text/plain, it is ignored and displayed
1242 * as HTML).
1244 * @relates type_query */
1245 void
1246 tp_display(struct type_query *type_query)
1248 struct view_state *vs;
1249 struct session *ses = type_query->ses;
1250 struct uri *loading_uri = ses->loading_uri;
1251 unsigned char *target_frame = ses->task.target.frame;
1253 ses->loading_uri = type_query->uri;
1254 ses->task.target.frame = type_query->target_frame;
1255 vs = ses_forward(ses, /* type_query->frame */ 0);
1256 if (vs) vs->plain = 1;
1257 ses->loading_uri = loading_uri;
1258 ses->task.target.frame = target_frame;
1260 if (/* !type_query->frame */ 1) {
1261 struct download *old = &type_query->download;
1262 struct download *new = &cur_loc(ses)->download;
1264 new->callback = (download_callback_T *) doc_loading_callback;
1265 new->data = ses;
1267 move_download(old, new, PRI_MAIN);
1270 display_timer(ses);
1271 done_type_query(type_query);
1274 /** The user chose "Open" when asked what to do with a file.
1275 * Or an external handler was found and it has been configured
1276 * to run without asking.
1278 * @relates type_query */
1279 static void
1280 tp_open(struct type_query *type_query)
1282 if (!type_query->external_handler || !*type_query->external_handler) {
1283 tp_display(type_query);
1284 return;
1287 if (type_query->uri->protocol == PROTOCOL_FILE && !type_query->cgi) {
1288 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1289 unsigned char *handler = NULL;
1291 if (file) {
1292 decode_uri(file);
1293 handler = subst_file(type_query->external_handler, file);
1294 mem_free(file);
1297 if (handler) {
1298 exec_on_terminal(type_query->ses->tab->term,
1299 handler, "",
1300 type_query->block ? TERM_EXEC_FG : TERM_EXEC_BG);
1301 mem_free(handler);
1304 done_type_query(type_query);
1305 return;
1308 continue_download(type_query, "");
1312 /*! @relates type_query */
1313 static void
1314 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
1316 /* [gettext_accelerator_context(.do_type_query)] */
1317 struct string filename;
1318 unsigned char *description;
1319 unsigned char *desc_sep;
1320 unsigned char *format, *text, *title;
1321 struct dialog *dlg;
1322 #define TYPE_QUERY_WIDGETS_COUNT 8
1323 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1324 struct terminal *term = type_query->ses->tab->term;
1325 struct memory_list *ml;
1326 struct dialog_data *dlg_data;
1327 int selected_widget;
1329 mem_free_set(&type_query->external_handler, NULL);
1331 if (handler) {
1332 type_query->block = handler->block;
1333 if (!handler->ask) {
1334 type_query->external_handler = stracpy(handler->program);
1335 tp_open(type_query);
1336 return;
1339 /* Start preparing for the type query dialog. */
1340 description = handler->description;
1341 desc_sep = *description ? "; " : "";
1342 title = N_("What to do?");
1344 } else {
1345 title = N_("Unknown type");
1346 description = "";
1347 desc_sep = "";
1350 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1351 if (!dlg) return;
1353 if (init_string(&filename)) {
1354 add_mime_filename_to_string(&filename, type_query->uri);
1356 /* Let's make the filename pretty for display & save */
1357 /* TODO: The filename can be the empty string here. See bug 396. */
1358 #ifdef CONFIG_UTF8
1359 if (term->utf8_cp)
1360 decode_uri_string(&filename);
1361 else
1362 #endif /* CONFIG_UTF8 */
1363 decode_uri_string_for_display(&filename);
1366 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1367 /* For "default directory index pages" with wrong content-type
1368 * the filename can be NULL, e.g. http://www.spamhaus.org in bug 396. */
1369 if (filename.length) {
1370 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1371 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1372 } else {
1373 format = _("What would you like to do with the file (type: %s%s%s)?", term);
1374 snprintf(text, MAX_STR_LEN, format, ct, desc_sep, description);
1377 done_string(&filename);
1379 dlg->title = _(title, term);
1380 dlg->layouter = generic_dialog_layouter;
1381 dlg->layout.padding_top = 1;
1382 dlg->layout.fit_datalen = 1;
1383 dlg->udata2 = type_query;
1385 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1387 /* Add input field or text widget with info about the program handler. */
1388 if (!get_cmd_opt_bool("anonymous")) {
1389 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1391 if (!field) {
1392 mem_free(dlg);
1393 return;
1396 if (handler && handler->program) {
1397 safe_strncpy(field, handler->program, MAX_STR_LEN);
1400 /* xgettext:no-c-format */
1401 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1402 0, 0, NULL, MAX_STR_LEN, field, NULL);
1403 type_query->external_handler = field;
1405 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1406 selected_widget = 3;
1408 } else if (handler) {
1409 unsigned char *field = text + MAX_STR_LEN;
1411 format = _("The file will be opened with the program '%s'.", term);
1412 snprintf(field, MAX_STR_LEN, format, handler->program);
1413 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1415 type_query->external_handler = stracpy(handler->program);
1416 if (!type_query->external_handler) {
1417 mem_free(dlg);
1418 return;
1421 widgets--;
1422 selected_widget = 2;
1424 } else {
1425 widgets -= 2;
1426 selected_widget = 1;
1429 /* Add buttons if they are both usable and allowed. */
1431 if (!get_cmd_opt_bool("anonymous") || handler) {
1432 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1433 (done_handler_T *) tp_open, type_query);
1434 } else {
1435 widgets--;
1438 if (!get_cmd_opt_bool("anonymous")) {
1439 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1440 (done_handler_T *) tp_save, type_query);
1441 } else {
1442 widgets--;
1445 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1446 (done_handler_T *) tp_display, type_query);
1448 if (type_query->cached && type_query->cached->head) {
1449 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1450 tp_show_header, type_query);
1451 } else {
1452 widgets--;
1455 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1456 (done_handler_T *) tp_cancel, type_query);
1458 add_dlg_end(dlg, widgets);
1460 ml = getml(dlg, (void *) NULL);
1461 if (!ml) {
1462 /* XXX: Assume that the allocated @external_handler will be
1463 * freed when releasing the @type_query. */
1464 mem_free(dlg);
1465 return;
1468 dlg_data = do_dialog(term, dlg, ml);
1469 /* Don't focus the text field; we want the user to be able
1470 * to select a button by typing the first letter of its label
1471 * without having to first leave the text field. */
1472 if (dlg_data) {
1473 select_widget_by_id(dlg_data, selected_widget);
1477 struct {
1478 unsigned char *type;
1479 unsigned int plain:1;
1480 } static const known_types[] = {
1481 { "text/html", 0 },
1482 { "text/plain", 1 },
1483 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1484 #if CONFIG_DOM
1485 { "application/docbook+xml", 1 },
1486 { "application/rss+xml", 0 },
1487 { "application/xbel+xml", 1 },
1488 { "application/xbel", 1 },
1489 { "application/x-xbel", 1 },
1490 #endif
1491 { NULL, 1 },
1495 setup_download_handler(struct session *ses, struct download *loading,
1496 struct cache_entry *cached, int frame)
1498 struct mime_handler *handler;
1499 struct view_state *vs;
1500 struct type_query *type_query;
1501 unsigned char *ctype = get_content_type(cached);
1502 int plaintext = 1;
1503 int ret = 0;
1504 int xwin, i;
1506 if (!ctype || !*ctype)
1507 goto plaintext_follow;
1509 for (i = 0; known_types[i].type; i++) {
1510 if (c_strcasecmp(ctype, known_types[i].type))
1511 continue;
1513 plaintext = known_types[i].plain;
1514 goto plaintext_follow;
1517 xwin = ses->tab->term->environment & ENV_XWIN;
1518 handler = get_mime_type_handler(ctype, xwin);
1520 if (!handler && strlen(ctype) >= 4 && !c_strncasecmp(ctype, "text", 4))
1521 goto plaintext_follow;
1523 type_query = init_type_query(ses, loading, cached);
1524 if (type_query) {
1525 ret = 1;
1526 #ifdef CONFIG_BITTORRENT
1527 /* A terrible waste of a good MIME handler here, but we want
1528 * to use the type_query this is easier. */
1529 if ((!c_strcasecmp(ctype, "application/x-bittorrent")
1530 || !c_strcasecmp(ctype, "application/x-torrent"))
1531 && !get_cmd_opt_bool("anonymous"))
1532 query_bittorrent_dialog(type_query);
1533 else
1534 #endif
1535 do_type_query(type_query, ctype, handler);
1538 mem_free_if(handler);
1540 return ret;
1542 plaintext_follow:
1543 vs = ses_forward(ses, frame);
1544 if (vs) vs->plain = plaintext;
1545 return 0;