Bug 770: Shorten lifetime of session.download_uri
[elinks/elinks-j605.git] / src / session / download.c
blobcc516a3a1dfb8d5be5001801435f568336ae63a3
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 /*! @note If this fails, the caller is responsible of freeing @a file
91 * and closing @a fd. */
92 struct file_download *
93 init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd)
95 struct file_download *file_download;
97 file_download = mem_calloc(1, sizeof(*file_download));
98 if (!file_download) return NULL;
100 /* Actually we could allow fragments in the URI and just change all the
101 * places that compares and shows the URI, but for now it is much
102 * easier this way. */
103 file_download->uri = get_composed_uri(uri, URI_BASE);
104 if (!file_download->uri) {
105 mem_free(file_download);
106 return NULL;
109 init_download_display(file_download);
111 file_download->file = file;
112 file_download->handle = fd;
114 file_download->download.callback = (download_callback_T *) download_data;
115 file_download->download.data = file_download;
116 file_download->ses = ses;
117 /* The tab may be closed, but we will still want to ie. open the
118 * handler on that terminal. */
119 file_download->term = ses->tab->term;
121 object_nolock(file_download, "file_download"); /* Debugging purpose. */
122 add_to_list(downloads, file_download);
124 return file_download;
128 void
129 abort_download(struct file_download *file_download)
131 #if 0
132 /* When hacking to cleanup the download code, remove lots of duplicated
133 * code and implement stuff from bug 435 we should reintroduce this
134 * assertion. Currently it will trigger often and shows that the
135 * download dialog code potentially could access free()d memory. */
136 assert(!is_object_used(file_download));
137 #endif
139 done_download_display(file_download);
141 if (file_download->ses)
142 check_questions_queue(file_download->ses);
144 if (file_download->dlg_data)
145 cancel_dialog(file_download->dlg_data, NULL);
146 cancel_download(&file_download->download, file_download->stop);
147 if (file_download->uri) done_uri(file_download->uri);
149 if (file_download->handle != -1) {
150 prealloc_truncate(file_download->handle, file_download->seek);
151 close(file_download->handle);
154 mem_free_if(file_download->external_handler);
155 if (file_download->file) {
156 if (file_download->delete) unlink(file_download->file);
157 mem_free(file_download->file);
159 del_from_list(file_download);
160 mem_free(file_download);
164 static void
165 kill_downloads_to_file(unsigned char *file)
167 struct file_download *file_download;
169 foreach (file_download, downloads) {
170 if (strcmp(file_download->file, file))
171 continue;
173 file_download = file_download->prev;
174 abort_download(file_download->next);
179 void
180 abort_all_downloads(void)
182 while (!list_empty(downloads))
183 abort_download(downloads.next);
187 void
188 destroy_downloads(struct session *ses)
190 struct file_download *file_download, *next;
191 struct session *s;
193 /* We are supposed to blat all downloads to external handlers belonging
194 * to @ses, but we will refuse to do so if there is another session
195 * bound to this terminal. That looks like the reasonable thing to do,
196 * fulfilling the principle of least astonishment. */
197 foreach (s, sessions) {
198 if (s == ses || s->tab->term != ses->tab->term)
199 continue;
201 foreach (file_download, downloads) {
202 if (file_download->ses != ses)
203 continue;
205 file_download->ses = s;
208 return;
211 foreachsafe (file_download, next, downloads) {
212 if (file_download->ses != ses)
213 continue;
215 if (!file_download->external_handler) {
216 file_download->ses = NULL;
217 continue;
220 abort_download(file_download);
224 void
225 detach_downloads_from_terminal(struct terminal *term)
227 struct file_download *file_download, *next;
229 assert(term != NULL);
230 if_assert_failed return;
232 foreachsafe (file_download, next, downloads) {
233 if (file_download->term != term)
234 continue;
236 if (!file_download->external_handler) {
237 file_download->term = NULL;
238 if (file_download->ses
239 && file_download->ses->tab->term == term)
240 file_download->ses = NULL;
241 continue;
244 abort_download(file_download);
248 static void
249 download_error_dialog(struct file_download *file_download, int saved_errno)
251 unsigned char *emsg = (unsigned char *) strerror(saved_errno);
252 struct session *ses = file_download->ses;
253 struct terminal *term = file_download->term;
255 if (!ses) return;
257 info_box(term, MSGBOX_FREE_TEXT,
258 N_("Download error"), ALIGN_CENTER,
259 msg_text(term, N_("Could not create file '%s':\n%s"),
260 file_download->file, emsg));
263 static int
264 write_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download)
266 struct fragment *frag;
268 if (file_download->download.progress && file_download->download.progress->seek) {
269 file_download->seek = file_download->download.progress->seek;
270 file_download->download.progress->seek = 0;
271 /* This is exclusive with the prealloc, thus we can perform
272 * this in front of that thing safely. */
273 if (lseek(file_download->handle, file_download->seek, SEEK_SET) < 0) {
274 download_error_dialog(file_download, errno);
275 return 0;
279 foreach (frag, cached->frag) {
280 off_t remain = file_download->seek - frag->offset;
281 int *h = &file_download->handle;
282 ssize_t w;
284 if (remain < 0 || frag->length <= remain)
285 continue;
287 #ifdef USE_OPEN_PREALLOC
288 if (!file_download->seek
289 && (!file_download->download.progress
290 || file_download->download.progress->size > 0)) {
291 close(*h);
292 *h = open_prealloc(file_download->file,
293 O_CREAT|O_WRONLY|O_TRUNC,
294 0666,
295 file_download->download.progress
296 ? file_download->download.progress->size
297 : cached->length);
298 if (*h == -1) {
299 download_error_dialog(file_download, errno);
300 return 0;
302 set_bin(*h);
304 #endif
306 w = safe_write(*h, frag->data + remain, frag->length - remain);
307 if (w == -1) {
308 download_error_dialog(file_download, errno);
309 return 0;
312 file_download->seek += w;
315 return 1;
318 static void
319 abort_download_and_beep(struct file_download *file_download, struct terminal *term)
321 if (term && get_opt_int("document.download.notify_bell")
322 + file_download->notify >= 2) {
323 beep_terminal(term);
326 abort_download(file_download);
329 static void
330 download_data_store(struct download *download, struct file_download *file_download)
332 struct terminal *term = file_download->term;
334 assert_terminal_ptr_not_dangling(term);
335 if_assert_failed term = file_download->term = NULL;
337 if (!term) {
338 /* No term here, so no beep. --Zas */
339 abort_download(file_download);
340 return;
343 if (is_in_progress_state(download->state)) {
344 if (file_download->dlg_data)
345 redraw_dialog(file_download->dlg_data, 1);
346 return;
349 if (!is_in_state(download->state, S_OK)) {
350 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
351 struct connection_state state = download->state;
353 abort_download_and_beep(file_download, term);
355 if (!url) return;
357 info_box(term, MSGBOX_FREE_TEXT,
358 N_("Download error"), ALIGN_CENTER,
359 msg_text(term, N_("Error downloading %s:\n\n%s"),
360 url, get_state_message(state, term)));
361 mem_free(url);
362 return;
365 if (file_download->external_handler) {
366 prealloc_truncate(file_download->handle, file_download->seek);
367 close(file_download->handle);
368 file_download->handle = -1;
369 exec_on_terminal(term, file_download->external_handler,
370 file_download->file,
371 file_download->block ? TERM_EXEC_FG : TERM_EXEC_BG);
372 file_download->delete = 0;
373 abort_download_and_beep(file_download, term);
374 return;
377 if (file_download->notify) {
378 unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC);
380 /* This is apparently a little racy. Deleting the box item will
381 * update the download browser _after_ the notification dialog
382 * has been drawn whereby it will be hidden. This should make
383 * the download browser update before launcing any
384 * notification. */
385 done_download_display(file_download);
387 if (url) {
388 info_box(term, MSGBOX_FREE_TEXT,
389 N_("Download"), ALIGN_CENTER,
390 msg_text(term, N_("Download complete:\n%s"), url));
391 mem_free(url);
395 if (file_download->remotetime
396 && get_opt_bool("document.download.set_original_time")) {
397 struct utimbuf foo;
399 foo.actime = foo.modtime = file_download->remotetime;
400 utime(file_download->file, &foo);
403 abort_download_and_beep(file_download, term);
406 static void
407 download_data(struct download *download, struct file_download *file_download)
409 struct cache_entry *cached = download->cached;
411 if (!cached || is_in_queued_state(download->state)) {
412 download_data_store(download, file_download);
413 return;
416 if (cached->last_modified)
417 file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1);
419 if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) {
420 cancel_download(&file_download->download, 0);
422 assertm(compare_uri(cached->uri, file_download->uri, 0),
423 "Redirecting using bad base URI");
425 done_uri(file_download->uri);
427 file_download->uri = get_uri_reference(cached->redirect);
428 file_download->download.state = connection_state(S_WAIT_REDIR);
430 if (file_download->dlg_data)
431 redraw_dialog(file_download->dlg_data, 1);
433 load_uri(file_download->uri, cached->uri, &file_download->download,
434 PRI_DOWNLOAD, CACHE_MODE_NORMAL,
435 download->progress ? download->progress->start : 0);
437 return;
440 if (!write_cache_entry_to_file(cached, file_download)) {
441 detach_connection(download, file_download->seek);
442 abort_download(file_download);
443 return;
446 detach_connection(download, file_download->seek);
447 download_data_store(download, file_download);
450 /** Type of the callback function that will be called when the user
451 * answers the question posed by lookup_unique_name().
453 * @param term
454 * The terminal on which the callback should display any windows.
455 * Comes directly from the @a term argument of lookup_unique_name().
457 * @param file
458 * The name of the local file to which the data should be downloaded,
459 * or NULL if the download should not begin. The callback is
460 * responsible of doing mem_free(@a file).
462 * @param data
463 * A pointer to any data that the callback cares about.
464 * Comes directly from the @a data argument of lookup_unique_name().
466 * @param resume
467 * Whether the user chose to resume downloading an existing file.
469 * @relates lun_hop */
470 typedef void lun_callback_T(struct terminal *term, unsigned char *file,
471 void *data, enum download_resume resume);
473 /** The user is being asked what to do when the local file for
474 * the download already exists. This structure is allocated by
475 * lookup_unique_name() and freed by each lun_* function:
476 * lun_alternate(), lun_cancel(), lun_overwrite(), and lun_resume(). */
477 struct lun_hop {
478 /** The terminal in which ELinks is asking the question.
479 * This gets passed to #callback. */
480 struct terminal *term;
482 /** The name of the local file into which the data was
483 * originally going to be downloaded, but which already
484 * exists. In this string, "~" has already been expanded
485 * to the home directory. The string must be freed with
486 * mem_free(). */
487 unsigned char *ofile;
489 /** An alternative file name that the user may choose instead
490 * of #ofile. The string must be freed with mem_free(). */
491 unsigned char *file;
493 /** This function will be called when the user answers. */
494 lun_callback_T *callback;
496 /** A pointer to be passed to #callback. If #resume includes
497 * ::DOWNLOAD_RESUME_ALLOWED, this must point to struct
498 * cdf_hop because the pointer can be read by lun_resume(),
499 * which assumes so. */
500 void *data;
502 /** Whether the download can be resumed.
503 * The ::DOWNLOAD_RESUME_SELECTED bit should be clear
504 * because otherwise there would have been no reason to
505 * ask the user and initialize this structure. */
506 enum download_resume resume;
509 /** Data saved by common_download() for the common_download_do()
510 * callback. */
511 struct cmdw_hop {
512 struct session *ses;
514 /** The URI from which the data will be downloaded. */
515 struct uri *download_uri;
517 /** The name of the local file to which the data will be
518 * downloaded. This is initially NULL, but its address is
519 * given to create_download_file(), which arranges for the
520 * pointer to be set before common_download_do() is called.
521 * The string must be freed with mem_free(). */
522 unsigned char *real_file;
525 /** Data saved by continue_download() for the continue_download_do()
526 * callback. */
527 struct codw_hop {
528 struct type_query *type_query;
530 /** The name of the local file to which the data will be
531 * downloaded. This is initially NULL, but its address is
532 * given to create_download_file(), which arranges for the
533 * pointer to be set before continue_download_do() is called.
534 * The string must be freed with mem_free(). */
535 unsigned char *real_file;
537 unsigned char *file;
540 /** Data saved by create_download_file() for the create_download_file_do()
541 * callback. */
542 struct cdf_hop {
543 /** Where to save the name of the file that was actually
544 * opened. One of the arguments of #callback is a file
545 * descriptor for this file. @c real_file can be NULL if
546 * #callback does not care about the name. */
547 unsigned char **real_file;
549 /** If nonzero, give only the user herself access to the file
550 * (even if the umask is looser), and create the file with
551 * @c O_EXCL unless resuming. */
552 int safe;
554 /** This function will be called when the file has been opened,
555 * or when it is known that the file will not be opened. */
556 cdf_callback_T *callback;
558 /** A pointer to be passed to #callback. */
559 void *data;
562 /** The use chose "Save under the alternative name" when asked where
563 * to download a file.
565 * lookup_unique_name() passes this function as a ::done_handler_T to
566 * msg_box().
568 * @relates lun_hop */
569 static void
570 lun_alternate(void *lun_hop_)
572 struct lun_hop *lun_hop = lun_hop_;
574 lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data,
575 lun_hop->resume);
576 mem_free_if(lun_hop->ofile);
577 mem_free(lun_hop);
580 /** The use chose "Cancel" when asked where to download a file.
582 * lookup_unique_name() passes this function as a ::done_handler_T to
583 * msg_box().
585 * @relates lun_hop */
586 static void
587 lun_cancel(void *lun_hop_)
589 struct lun_hop *lun_hop = lun_hop_;
591 lun_hop->callback(lun_hop->term, NULL, lun_hop->data,
592 lun_hop->resume);
593 mem_free_if(lun_hop->ofile);
594 mem_free_if(lun_hop->file);
595 mem_free(lun_hop);
598 /** The use chose "Overwrite the original file" when asked where to
599 * download a file.
601 * lookup_unique_name() passes this function as a ::done_handler_T to
602 * msg_box().
604 * @relates lun_hop */
605 static void
606 lun_overwrite(void *lun_hop_)
608 struct lun_hop *lun_hop = lun_hop_;
610 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data,
611 lun_hop->resume);
612 mem_free_if(lun_hop->file);
613 mem_free(lun_hop);
616 /** The user chose "Resume download of the original file" when asked
617 * where to download a file.
619 * lookup_unique_name() passes this function as a ::done_handler_T to
620 * msg_box().
622 * @relates lun_hop */
623 static void
624 lun_resume(void *lun_hop_)
626 struct lun_hop *lun_hop = lun_hop_;
628 lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data,
629 lun_hop->resume | DOWNLOAD_RESUME_SELECTED);
630 mem_free_if(lun_hop->file);
631 mem_free(lun_hop);
635 /** If attempting to download to an existing file, perhaps ask
636 * the user whether to resume, overwrite, or save elsewhere.
637 * This function constructs a struct lun_hop, which will be freed
638 * when the user answers the question.
640 * @param term
641 * The terminal in which this function should show its UI.
643 * @param[in] ofile
644 * A proposed name for the local file to which the data would be
645 * downloaded. "~" here refers to the home directory.
646 * lookup_unique_name() treats this original string as read-only.
648 * @param[in] resume
649 * Indicates if the user already chose to resume downloading,
650 * before ELinks even asked for the file name.
651 * See ::ACT_MAIN_LINK_DOWNLOAD_RESUME.
653 * @param callback
654 * Will be called when the user answers, or right away if the question
655 * need not or cannot be asked.
657 * @param data
658 * A pointer to be passed to @a callback. If @a resume includes
659 * ::DOWNLOAD_RESUME_ALLOWED, this must point to struct cdf_hop
660 * because the pointer can be read by lun_resume(), which assumes so.
662 * @relates lun_hop */
663 static void
664 lookup_unique_name(struct terminal *term, unsigned char *ofile,
665 enum download_resume resume,
666 lun_callback_T *callback, void *data)
668 /* [gettext_accelerator_context(.lookup_unique_name)] */
669 struct lun_hop *lun_hop;
670 unsigned char *file;
671 int overwrite;
673 ofile = expand_tilde(ofile);
675 /* Minor code duplication to prevent useless call to get_opt_int()
676 * if possible. --Zas */
677 if (resume & DOWNLOAD_RESUME_SELECTED) {
678 callback(term, ofile, data, resume);
679 return;
682 /* !overwrite means always silently overwrite, which may be admitelly
683 * indeed a little confusing ;-) */
684 overwrite = get_opt_int("document.download.overwrite");
685 if (!overwrite) {
686 /* Nothing special to do... */
687 callback(term, ofile, data, resume);
688 return;
691 /* Check if file is a directory, and use a default name if it's the
692 * case. */
693 if (file_is_dir(ofile)) {
694 info_box(term, MSGBOX_FREE_TEXT,
695 N_("Download error"), ALIGN_CENTER,
696 msg_text(term, N_("'%s' is a directory."),
697 ofile));
698 mem_free(ofile);
699 callback(term, NULL, data, resume & ~DOWNLOAD_RESUME_SELECTED);
700 return;
703 /* Check if the file already exists (file != ofile). */
704 file = get_unique_name(ofile);
706 if (!file || overwrite == 1 || file == ofile) {
707 /* Still nothing special to do... */
708 if (file != ofile) mem_free(ofile);
709 callback(term, file, data, resume & ~DOWNLOAD_RESUME_SELECTED);
710 return;
713 /* overwrite == 2 (ask) and file != ofile (=> original file already
714 * exists) */
716 lun_hop = mem_calloc(1, sizeof(*lun_hop));
717 if (!lun_hop) {
718 if (file != ofile) mem_free(file);
719 mem_free(ofile);
720 callback(term, NULL, data, resume & ~DOWNLOAD_RESUME_SELECTED);
721 return;
723 lun_hop->term = term;
724 lun_hop->ofile = ofile;
725 lun_hop->file = (file != ofile) ? file : stracpy(ofile);
726 lun_hop->callback = callback;
727 lun_hop->data = data;
728 lun_hop->resume = resume;
730 msg_box(term, NULL, MSGBOX_FREE_TEXT,
731 N_("File exists"), ALIGN_CENTER,
732 msg_text(term, N_("This file already exists:\n"
733 "%s\n\n"
734 "The alternative filename is:\n"
735 "%s"),
736 empty_string_or_(lun_hop->ofile),
737 empty_string_or_(file)),
738 lun_hop, 4,
739 MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER),
740 MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0),
741 MSG_BOX_BUTTON((resume & DOWNLOAD_RESUME_ALLOWED
742 ? N_("~Resume download of the original file")
743 : NULL),
744 lun_resume, 0),
745 MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC));
750 /** Now that the final name of the download file has been chosen,
751 * open the file and call the ::cdf_callback_T that was originally
752 * given to create_download_file().
754 * create_download_file() passes this function as a ::lun_callback_T
755 * to lookup_unique_name().
757 * @relates cdf_hop */
758 static void
759 create_download_file_do(struct terminal *term, unsigned char *file,
760 void *data, enum download_resume resume)
762 struct cdf_hop *cdf_hop = data;
763 unsigned char *wd;
764 int h = -1;
765 int saved_errno;
766 #ifdef NO_FILE_SECURITY
767 int sf = 0;
768 #else
769 int sf = cdf_hop->safe;
770 #endif
772 if (!file) goto finish;
774 wd = get_cwd();
775 set_cwd(term->cwd);
777 /* Create parent directories if needed. */
778 mkalldirs(file);
780 /* O_APPEND means repositioning at the end of file before each write(),
781 * thus ignoring seek()s and that can hide mysterious bugs. IMHO.
782 * --pasky */
783 h = open(file, O_CREAT | O_WRONLY
784 | (resume & DOWNLOAD_RESUME_SELECTED ? 0 : O_TRUNC)
785 | (sf && !(resume & DOWNLOAD_RESUME_SELECTED) ? O_EXCL : 0),
786 sf ? 0600 : 0666);
787 saved_errno = errno; /* Saved in case of ... --Zas */
789 if (wd) {
790 set_cwd(wd);
791 mem_free(wd);
794 if (h == -1) {
795 info_box(term, MSGBOX_FREE_TEXT,
796 N_("Download error"), ALIGN_CENTER,
797 msg_text(term, N_("Could not create file '%s':\n%s"),
798 file, strerror(saved_errno)));
800 mem_free(file);
801 goto finish;
803 } else {
804 set_bin(h);
806 if (!cdf_hop->safe) {
807 unsigned char *download_dir = get_opt_str("document.download.directory");
808 int i;
810 safe_strncpy(download_dir, file, MAX_STR_LEN);
812 /* Find the used directory so it's available in history */
813 for (i = strlen(download_dir); i >= 0; i--)
814 if (dir_sep(download_dir[i]))
815 break;
816 download_dir[i + 1] = 0;
820 if (cdf_hop->real_file)
821 *cdf_hop->real_file = file;
822 else
823 mem_free(file);
825 finish:
826 cdf_hop->callback(term, h, cdf_hop->data, resume);
827 mem_free(cdf_hop);
830 /** Create a file to which data can be downloaded.
831 * This function constructs a struct cdf_hop that will be freed
832 * when @a callback returns.
834 * @param term
835 * If any dialog boxes are needed, show them in this terminal.
837 * @param fi
838 * A proposed name for the local file to which the data would be
839 * downloaded. "~" here refers to the home directory.
840 * create_download_file() treats this original string as read-only.
842 * @param real_file
843 * If non-NULL, prepare to save in *@a real_file the name of the local
844 * file that was eventually opened. @a callback must then arrange for
845 * this string to be freed with mem_free().
847 * @param safe
848 * If nonzero, give only the user herself access to the file (even if
849 * the umask is looser), and create the file with @c O_EXCL unless
850 * resuming.
852 * @param resume
853 * Whether the download can be resumed, and whether the user already
854 * asked for it to be resumed.
856 * @param callback
857 * This function will be called when the file has been opened,
858 * or when it is known that the file will not be opened.
860 * @param data
861 * A pointer to be passed to @a callback.
863 * @relates cdf_hop */
864 void
865 create_download_file(struct terminal *term, unsigned char *fi,
866 unsigned char **real_file, int safe,
867 enum download_resume resume,
868 cdf_callback_T *callback, void *data)
870 struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop));
871 unsigned char *wd;
873 if (!cdf_hop) {
874 callback(term, -1, data, resume & ~DOWNLOAD_RESUME_SELECTED);
875 return;
878 cdf_hop->real_file = real_file;
879 cdf_hop->safe = safe;
880 cdf_hop->callback = callback;
881 cdf_hop->data = data;
883 /* FIXME: The wd bussiness is probably useless here? --pasky */
884 wd = get_cwd();
885 set_cwd(term->cwd);
887 /* Also the tilde will be expanded here. */
888 lookup_unique_name(term, fi, resume, create_download_file_do, cdf_hop);
890 if (wd) {
891 set_cwd(wd);
892 mem_free(wd);
897 static unsigned char *
898 get_temp_name(struct uri *uri)
900 struct string name;
901 unsigned char *extension;
902 /* FIXME
903 * We use tempnam() here, which is unsafe (race condition), for now.
904 * This should be changed at some time, but it needs an in-depth work
905 * of whole download code. --Zas */
906 unsigned char *nm = tempnam(NULL, ELINKS_TEMPNAME_PREFIX);
908 if (!nm) return NULL;
910 if (!init_string(&name)) {
911 free(nm);
912 return NULL;
915 add_to_string(&name, nm);
916 free(nm);
918 extension = get_extension_from_uri(uri);
919 if (extension) {
920 add_shell_safe_to_string(&name, extension, strlen(extension));
921 mem_free(extension);
924 return name.source;
928 static unsigned char *
929 subst_file(unsigned char *prog, unsigned char *file)
931 struct string name;
932 /* When there is no %s in the mailcap entry, the handler program reads
933 * data from stdin instead of a file. */
934 int input = 1;
936 if (!init_string(&name)) return NULL;
938 while (*prog) {
939 int p;
941 for (p = 0; prog[p] && prog[p] != '%'; p++);
943 add_bytes_to_string(&name, prog, p);
944 prog += p;
946 if (*prog == '%') {
947 input = 0;
948 #if defined(HAVE_CYGWIN_CONV_TO_FULL_WIN32_PATH)
949 #ifdef MAX_PATH
950 unsigned char new_path[MAX_PATH];
951 #else
952 unsigned char new_path[1024];
953 #endif
955 cygwin_conv_to_full_win32_path(file, new_path);
956 add_to_string(&name, new_path);
957 #else
958 add_shell_quoted_to_string(&name, file, strlen(file));
959 #endif
960 prog++;
964 if (input) {
965 struct string s;
967 if (init_string(&s)) {
968 add_to_string(&s, "/bin/cat ");
969 add_shell_quoted_to_string(&s, file, strlen(file));
970 add_to_string(&s, " | ");
971 add_string_to_string(&s, &name);
972 done_string(&name);
973 return s.source;
976 return name.source;
981 /*! common_download() passes this function as a ::cdf_callback_T to
982 * create_download_file().
984 * @relates cmdw_hop */
985 static void
986 common_download_do(struct terminal *term, int fd, void *data,
987 enum download_resume resume)
989 struct file_download *file_download;
990 struct cmdw_hop *cmdw_hop = data;
991 struct uri *download_uri = cmdw_hop->download_uri;
992 unsigned char *file = cmdw_hop->real_file;
993 struct session *ses = cmdw_hop->ses;
994 struct stat buf;
996 mem_free(cmdw_hop);
998 if (!file || fstat(fd, &buf)) goto finish;
1000 file_download = init_file_download(download_uri, ses, file, fd);
1001 if (!file_download) goto finish;
1002 /* If init_file_download succeeds, it takes ownership of file
1003 * and fd. */
1004 file = NULL;
1005 fd = -1;
1007 if (resume & DOWNLOAD_RESUME_SELECTED)
1008 file_download->seek = buf.st_size;
1010 display_download(ses->tab->term, file_download, ses);
1012 load_uri(file_download->uri, ses->referrer, &file_download->download,
1013 PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
1015 finish:
1016 mem_free_if(file);
1017 if (fd != -1) close(fd);
1018 done_uri(download_uri);
1021 /** Begin or resume downloading from session.download_uri to the
1022 * @a file specified by the user.
1024 * This function contains the code shared between start_download() and
1025 * resume_download().
1027 * @relates cmdw_hop */
1028 static void
1029 common_download(struct session *ses, unsigned char *file,
1030 enum download_resume resume)
1032 struct cmdw_hop *cmdw_hop;
1034 if (!ses->download_uri) return;
1036 cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
1037 if (!cmdw_hop) return;
1038 cmdw_hop->ses = ses;
1039 cmdw_hop->download_uri = ses->download_uri;
1040 ses->download_uri = NULL;
1042 kill_downloads_to_file(file);
1044 create_download_file(ses->tab->term, file, &cmdw_hop->real_file, 0,
1045 resume, common_download_do, cmdw_hop);
1048 /** Begin downloading from session.download_uri to the @a file
1049 * specified by the user.
1051 * The ::ACT_MAIN_SAVE_AS, ::ACT_MAIN_SAVE_URL_AS,
1052 * ::ACT_MAIN_LINK_DOWNLOAD, and ::ACT_MAIN_LINK_DOWNLOAD_IMAGE
1053 * actions pass this function as the @c std callback to query_file().
1055 * @relates cmdw_hop */
1056 void
1057 start_download(void *ses, unsigned char *file)
1059 common_download(ses, file,
1060 DOWNLOAD_RESUME_ALLOWED);
1064 /** Resume downloading from session.download_uri to the @a file
1065 * specified by the user.
1067 * The ::ACT_MAIN_LINK_DOWNLOAD_RESUME action passes this function as
1068 * the @c std callback to query_file().
1070 * @relates cmdw_hop */
1071 void
1072 resume_download(void *ses, unsigned char *file)
1074 common_download(ses, file,
1075 DOWNLOAD_RESUME_ALLOWED | DOWNLOAD_RESUME_SELECTED);
1078 /** Resume downloading a file, based on information in struct
1079 * codw_hop. This function actually starts a new download from the
1080 * current end of the file, even though a download from the beginning
1081 * is already in progress at codw_hop->type_query->download. The
1082 * caller will cancel the preexisting download after this function
1083 * returns.
1085 * @relates codw_hop */
1086 static void
1087 transform_codw_to_cmdw(struct terminal *term, int fd,
1088 struct codw_hop *codw_hop,
1089 enum download_resume resume)
1091 struct type_query *type_query = codw_hop->type_query;
1092 struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop));
1094 if (!cmdw_hop) return;
1096 cmdw_hop->ses = type_query->ses;
1097 cmdw_hop->download_uri = get_uri_reference(type_query->uri);
1098 cmdw_hop->real_file = codw_hop->real_file;
1099 codw_hop->real_file = NULL;
1101 common_download_do(term, fd, cmdw_hop, resume);
1104 /*! continue_download() passes this function as a ::cdf_callback_T to
1105 * create_download_file().
1107 * @relates codw_hop */
1108 static void
1109 continue_download_do(struct terminal *term, int fd, void *data,
1110 enum download_resume resume)
1112 struct codw_hop *codw_hop = data;
1113 struct file_download *file_download = NULL;
1114 struct type_query *type_query;
1116 assert(codw_hop);
1117 assert(codw_hop->type_query);
1118 assert(codw_hop->type_query->uri);
1119 assert(codw_hop->type_query->ses);
1121 type_query = codw_hop->type_query;
1122 if (!codw_hop->real_file) goto cancel;
1124 if (resume & DOWNLOAD_RESUME_SELECTED) {
1125 transform_codw_to_cmdw(term, fd, codw_hop, resume);
1126 goto cancel;
1129 file_download = init_file_download(type_query->uri, type_query->ses,
1130 codw_hop->real_file, fd);
1131 if (!file_download) goto cancel;
1132 /* If init_file_download succeeds, it takes ownership of
1133 * codw_hop->real_file and fd. */
1134 codw_hop->real_file = NULL;
1135 fd = -1;
1137 if (type_query->external_handler) {
1138 file_download->external_handler = subst_file(type_query->external_handler,
1139 codw_hop->file);
1140 file_download->delete = 1;
1141 mem_free(codw_hop->file);
1142 mem_free_set(&type_query->external_handler, NULL);
1145 file_download->block = !!type_query->block;
1147 /* Done here and not in init_file_download() so that the external
1148 * handler can become initialized. */
1149 display_download(term, file_download, type_query->ses);
1151 move_download(&type_query->download, &file_download->download, PRI_DOWNLOAD);
1152 done_type_query(type_query);
1154 mem_free(codw_hop);
1155 return;
1157 cancel:
1158 mem_free_if(codw_hop->real_file);
1159 if (fd != -1) close(fd);
1160 if (type_query->external_handler) mem_free_if(codw_hop->file);
1161 tp_cancel(type_query);
1162 mem_free(codw_hop);
1165 /** When asked what to do with a file, the user chose to download it
1166 * to a local file named @a file.
1167 * Or an external handler was selected, in which case
1168 * type_query.external_handler is non-NULL and @a file does not
1169 * matter because this function will generate a name.
1171 * tp_save() passes this function as the @c std callback to query_file().
1173 * @relates codw_hop */
1174 static void
1175 continue_download(void *data, unsigned char *file)
1177 struct type_query *type_query = data;
1178 struct codw_hop *codw_hop = mem_calloc(1, sizeof(*codw_hop));
1180 if (!codw_hop) {
1181 tp_cancel(type_query);
1182 return;
1185 if (type_query->external_handler) {
1186 /* FIXME: get_temp_name() calls tempnam(). --Zas */
1187 file = get_temp_name(type_query->uri);
1188 if (!file) {
1189 mem_free(codw_hop);
1190 tp_cancel(type_query);
1191 return;
1195 codw_hop->type_query = type_query;
1196 codw_hop->file = file;
1198 kill_downloads_to_file(file);
1200 create_download_file(type_query->ses->tab->term, file,
1201 &codw_hop->real_file,
1202 !!type_query->external_handler,
1203 DOWNLOAD_RESUME_ALLOWED,
1204 continue_download_do, codw_hop);
1208 /** Prepare to ask the user what to do with a file, but don't display
1209 * the window yet. To display it, do_type_query() must be called
1210 * separately. setup_download_handler() takes care of that.
1212 * @relates type_query */
1213 static struct type_query *
1214 init_type_query(struct session *ses, struct download *download,
1215 struct cache_entry *cached)
1217 struct type_query *type_query;
1219 /* There can be only one ... */
1220 foreach (type_query, ses->type_queries)
1221 if (compare_uri(type_query->uri, ses->loading_uri, 0))
1222 return NULL;
1224 type_query = mem_calloc(1, sizeof(*type_query));
1225 if (!type_query) return NULL;
1227 type_query->uri = get_uri_reference(ses->loading_uri);
1228 type_query->ses = ses;
1229 type_query->target_frame = null_or_stracpy(ses->task.target.frame);
1231 type_query->cached = cached;
1232 type_query->cgi = cached->cgi;
1233 object_lock(type_query->cached);
1235 move_download(download, &type_query->download, PRI_MAIN);
1236 download->state = connection_state(S_OK);
1238 add_to_list(ses->type_queries, type_query);
1240 return type_query;
1243 /** Cancel any download started for @a type_query, remove the structure
1244 * from the session.type_queries list, and free it.
1246 * @relates type_query */
1247 void
1248 done_type_query(struct type_query *type_query)
1250 /* Unregister any active download */
1251 cancel_download(&type_query->download, 0);
1253 object_unlock(type_query->cached);
1254 done_uri(type_query->uri);
1255 mem_free_if(type_query->external_handler);
1256 mem_free_if(type_query->target_frame);
1257 del_from_list(type_query);
1258 mem_free(type_query);
1262 /** The user chose "Cancel" when asked what to do with a file,
1263 * or the type query was cancelled for some other reason.
1265 * do_type_query() and bittorrent_query_callback() pass this function
1266 * as a ::done_handler_T to add_dlg_ok_button(), and tp_save() passes
1267 * this function as a @c cancel callback to query_file().
1269 * @relates type_query */
1270 void
1271 tp_cancel(void *data)
1273 struct type_query *type_query = data;
1275 /* XXX: Should we really abort? (1 vs 0 as the last param) --pasky */
1276 cancel_download(&type_query->download, 1);
1277 done_type_query(type_query);
1281 /** The user chose "Save" when asked what to do with a file.
1282 * Now ask her where to save the file.
1284 * do_type_query() and bittorrent_query_callback() pass this function
1285 * as a ::done_handler_T to add_dlg_ok_button().
1287 * @relates type_query */
1288 void
1289 tp_save(struct type_query *type_query)
1291 mem_free_set(&type_query->external_handler, NULL);
1292 query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1);
1295 /** The user chose "Show header" when asked what to do with a file.
1297 * do_type_query() passes this function as a ::widget_handler_T to
1298 * add_dlg_button(). Unlike with add_dlg_ok_button(), pressing this
1299 * button does not close the dialog box. This way, the user can
1300 * first examine the header and then choose what to do.
1302 * @relates type_query */
1303 static widget_handler_status_T
1304 tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
1306 struct type_query *type_query = widget_data->widget->data;
1308 cached_header_dialog(type_query->ses, type_query->cached);
1310 return EVENT_PROCESSED;
1314 /** The user chose "Display" when asked what to do with a file,
1315 * or she chose "Open" and there is no external handler.
1317 * do_type_query() and bittorrent_query_callback() pass this function
1318 * as a ::done_handler_T to add_dlg_ok_button().
1320 * @bug FIXME: We need to modify this function to take frame data
1321 * instead, as we want to use this function for frames as well (now,
1322 * when frame has content type text/plain, it is ignored and displayed
1323 * as HTML).
1325 * @relates type_query */
1326 void
1327 tp_display(struct type_query *type_query)
1329 struct view_state *vs;
1330 struct session *ses = type_query->ses;
1331 struct uri *loading_uri = ses->loading_uri;
1332 unsigned char *target_frame = ses->task.target.frame;
1334 ses->loading_uri = type_query->uri;
1335 ses->task.target.frame = type_query->target_frame;
1336 vs = ses_forward(ses, /* type_query->frame */ 0);
1337 if (vs) vs->plain = 1;
1338 ses->loading_uri = loading_uri;
1339 ses->task.target.frame = target_frame;
1341 if (/* !type_query->frame */ 1) {
1342 struct download *old = &type_query->download;
1343 struct download *new = &cur_loc(ses)->download;
1345 new->callback = (download_callback_T *) doc_loading_callback;
1346 new->data = ses;
1348 move_download(old, new, PRI_MAIN);
1351 display_timer(ses);
1352 done_type_query(type_query);
1355 /** The user chose "Open" when asked what to do with a file.
1356 * Or an external handler was found and it has been configured
1357 * to run without asking.
1359 * do_type_query() passes this function as a ::done_handler_T to
1360 * add_dlg_ok_button().
1362 * @relates type_query */
1363 static void
1364 tp_open(struct type_query *type_query)
1366 if (!type_query->external_handler || !*type_query->external_handler) {
1367 tp_display(type_query);
1368 return;
1371 if (type_query->uri->protocol == PROTOCOL_FILE && !type_query->cgi) {
1372 unsigned char *file = get_uri_string(type_query->uri, URI_PATH);
1373 unsigned char *handler = NULL;
1375 if (file) {
1376 decode_uri(file);
1377 handler = subst_file(type_query->external_handler, file);
1378 mem_free(file);
1381 if (handler) {
1382 exec_on_terminal(type_query->ses->tab->term,
1383 handler, "",
1384 type_query->block ? TERM_EXEC_FG : TERM_EXEC_BG);
1385 mem_free(handler);
1388 done_type_query(type_query);
1389 return;
1392 continue_download(type_query, "");
1396 /*! Ask the user what to do with a file.
1398 * This function does not support BitTorrent downloads.
1399 * For those, query_bittorrent_dialog() must be called instead.
1400 * setup_download_handler() takes care of this.
1402 * @relates type_query */
1403 static void
1404 do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler)
1406 /* [gettext_accelerator_context(.do_type_query)] */
1407 struct string filename;
1408 unsigned char *description;
1409 unsigned char *desc_sep;
1410 unsigned char *format, *text, *title;
1411 struct dialog *dlg;
1412 #define TYPE_QUERY_WIDGETS_COUNT 8
1413 int widgets = TYPE_QUERY_WIDGETS_COUNT;
1414 struct terminal *term = type_query->ses->tab->term;
1415 struct memory_list *ml;
1416 struct dialog_data *dlg_data;
1417 int selected_widget;
1419 mem_free_set(&type_query->external_handler, NULL);
1421 if (handler) {
1422 type_query->block = handler->block;
1423 if (!handler->ask) {
1424 type_query->external_handler = stracpy(handler->program);
1425 tp_open(type_query);
1426 return;
1429 /* Start preparing for the type query dialog. */
1430 description = handler->description;
1431 desc_sep = *description ? "; " : "";
1432 title = N_("What to do?");
1434 } else {
1435 title = N_("Unknown type");
1436 description = "";
1437 desc_sep = "";
1440 dlg = calloc_dialog(TYPE_QUERY_WIDGETS_COUNT, MAX_STR_LEN * 2);
1441 if (!dlg) return;
1443 if (init_string(&filename)) {
1444 add_mime_filename_to_string(&filename, type_query->uri);
1446 /* Let's make the filename pretty for display & save */
1447 /* TODO: The filename can be the empty string here. See bug 396. */
1448 #ifdef CONFIG_UTF8
1449 if (term->utf8_cp)
1450 decode_uri_string(&filename);
1451 else
1452 #endif /* CONFIG_UTF8 */
1453 decode_uri_string_for_display(&filename);
1456 text = get_dialog_offset(dlg, TYPE_QUERY_WIDGETS_COUNT);
1457 /* For "default directory index pages" with wrong content-type
1458 * the filename can be NULL, e.g. http://www.spamhaus.org in bug 396. */
1459 if (filename.length) {
1460 format = _("What would you like to do with the file '%s' (type: %s%s%s)?", term);
1461 snprintf(text, MAX_STR_LEN, format, filename.source, ct, desc_sep, description);
1462 } else {
1463 format = _("What would you like to do with the file (type: %s%s%s)?", term);
1464 snprintf(text, MAX_STR_LEN, format, ct, desc_sep, description);
1467 done_string(&filename);
1469 dlg->title = _(title, term);
1470 dlg->layouter = generic_dialog_layouter;
1471 dlg->layout.padding_top = 1;
1472 dlg->layout.fit_datalen = 1;
1473 dlg->udata2 = type_query;
1475 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
1477 /* Add input field or text widget with info about the program handler. */
1478 if (!get_cmd_opt_bool("anonymous")) {
1479 unsigned char *field = mem_calloc(1, MAX_STR_LEN);
1481 if (!field) {
1482 mem_free(dlg);
1483 return;
1486 if (handler && handler->program) {
1487 safe_strncpy(field, handler->program, MAX_STR_LEN);
1490 /* xgettext:no-c-format */
1491 add_dlg_field(dlg, _("Program ('%' will be replaced by the filename)", term),
1492 0, 0, NULL, MAX_STR_LEN, field, NULL);
1493 type_query->external_handler = field;
1495 add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block);
1496 selected_widget = 3;
1498 } else if (handler) {
1499 unsigned char *field = text + MAX_STR_LEN;
1501 format = _("The file will be opened with the program '%s'.", term);
1502 snprintf(field, MAX_STR_LEN, format, handler->program);
1503 add_dlg_text(dlg, field, ALIGN_LEFT, 0);
1505 type_query->external_handler = stracpy(handler->program);
1506 if (!type_query->external_handler) {
1507 mem_free(dlg);
1508 return;
1511 widgets--;
1512 selected_widget = 2;
1514 } else {
1515 widgets -= 2;
1516 selected_widget = 1;
1519 /* Add buttons if they are both usable and allowed. */
1521 if (!get_cmd_opt_bool("anonymous") || handler) {
1522 add_dlg_ok_button(dlg, _("~Open", term), B_ENTER,
1523 (done_handler_T *) tp_open, type_query);
1524 } else {
1525 widgets--;
1528 if (!get_cmd_opt_bool("anonymous")) {
1529 add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
1530 (done_handler_T *) tp_save, type_query);
1531 } else {
1532 widgets--;
1535 add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
1536 (done_handler_T *) tp_display, type_query);
1538 if (type_query->cached && type_query->cached->head) {
1539 add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
1540 tp_show_header, type_query);
1541 } else {
1542 widgets--;
1545 add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
1546 (done_handler_T *) tp_cancel, type_query);
1548 add_dlg_end(dlg, widgets);
1550 ml = getml(dlg, (void *) NULL);
1551 if (!ml) {
1552 /* XXX: Assume that the allocated @external_handler will be
1553 * freed when releasing the @type_query. */
1554 mem_free(dlg);
1555 return;
1558 dlg_data = do_dialog(term, dlg, ml);
1559 /* Don't focus the text field; we want the user to be able
1560 * to select a button by typing the first letter of its label
1561 * without having to first leave the text field. */
1562 if (dlg_data) {
1563 select_widget_by_id(dlg_data, selected_widget);
1567 struct {
1568 unsigned char *type;
1569 unsigned int plain:1;
1570 } static const known_types[] = {
1571 { "text/html", 0 },
1572 { "text/plain", 1 },
1573 { "application/xhtml+xml", 0 }, /* RFC 3236 */
1574 #if CONFIG_DOM
1575 { "application/docbook+xml", 1 },
1576 { "application/rss+xml", 0 },
1577 { "application/xbel+xml", 1 },
1578 { "application/xbel", 1 },
1579 { "application/x-xbel", 1 },
1580 #endif
1581 { NULL, 1 },
1584 /*! @relates type_query */
1586 setup_download_handler(struct session *ses, struct download *loading,
1587 struct cache_entry *cached, int frame)
1589 struct mime_handler *handler;
1590 struct view_state *vs;
1591 struct type_query *type_query;
1592 unsigned char *ctype = get_content_type(cached);
1593 int plaintext = 1;
1594 int ret = 0;
1595 int xwin, i;
1597 if (!ctype || !*ctype)
1598 goto plaintext_follow;
1600 for (i = 0; known_types[i].type; i++) {
1601 if (c_strcasecmp(ctype, known_types[i].type))
1602 continue;
1604 plaintext = known_types[i].plain;
1605 goto plaintext_follow;
1608 xwin = ses->tab->term->environment & ENV_XWIN;
1609 handler = get_mime_type_handler(ctype, xwin);
1611 if (!handler && strlen(ctype) >= 4 && !c_strncasecmp(ctype, "text", 4))
1612 goto plaintext_follow;
1614 type_query = init_type_query(ses, loading, cached);
1615 if (type_query) {
1616 ret = 1;
1617 #ifdef CONFIG_BITTORRENT
1618 /* A terrible waste of a good MIME handler here, but we want
1619 * to use the type_query this is easier. */
1620 if ((!c_strcasecmp(ctype, "application/x-bittorrent")
1621 || !c_strcasecmp(ctype, "application/x-torrent"))
1622 && !get_cmd_opt_bool("anonymous"))
1623 query_bittorrent_dialog(type_query);
1624 else
1625 #endif
1626 do_type_query(type_query, ctype, handler);
1629 mem_free_if(handler);
1631 return ret;
1633 plaintext_follow:
1634 vs = ses_forward(ses, frame);
1635 if (vs) vs->plain = plaintext;
1636 return 0;