3 * Command line processing
6 * Copyright (C) 1996-2007,2010,2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1999-2007 Thomas Roessler <roessler@does-not-exist.org>
8 * Copyright (C) 2004 g10 Code GmbH
9 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
12 * This program is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free Software
14 * Foundation, either version 2 of the License, or (at your option) any later
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * You should have received a copy of the GNU General Public License along with
23 * this program. If not, see <http://www.gnu.org/licenses/>.
27 * @page main Command line processing
29 * Command line processing
33 #define GNULIB_defined_setlocale
48 #include "mutt/mutt.h"
49 #include "address/lib.h"
50 #include "config/lib.h"
51 #include "email/lib.h"
53 #include "conn/conn.h"
64 #include "mutt_attach.h"
65 #include "mutt_commands.h"
66 #include "mutt_curses.h"
67 #include "mutt_history.h"
68 #include "mutt_logging.h"
69 #include "mutt_mailbox.h"
70 #include "mutt_menu.h"
71 #include "mutt_window.h"
75 #include "ncrypt/ncrypt.h"
89 #include "imap/imap.h"
92 #include "nntp/nntp.h"
95 #include "autocrypt/autocrypt.h"
98 /* These Config Variables are only used in main.c */
99 bool C_ResumeEditedDraftFiles; ///< Config: Resume editing previously saved draft files
102 typedef uint8_t CliFlags; ///< Flags for command line options, e.g. #MUTT_CLI_IGNORE
103 #define MUTT_CLI_NO_FLAGS 0 ///< No flags are set
104 #define MUTT_CLI_IGNORE (1 << 0) ///< -z Open first mailbox if it has mail
105 #define MUTT_CLI_MAILBOX (1 << 1) ///< -Z Open first mailbox if is has new mail
106 #define MUTT_CLI_NOSYSRC (1 << 2) ///< -n Do not read the system-wide config file
107 #define MUTT_CLI_RO (1 << 3) ///< -R Open mailbox in read-only mode
108 #define MUTT_CLI_SELECT (1 << 4) ///< -y Start with a list of all mailboxes
110 #define MUTT_CLI_NEWS (1 << 5) ///< -g/-G Start with a list of all newsgroups
115 * test_parse_set - Test the config parsing
117 static void test_parse_set(void)
119 const char *vars[] = {
123 "mbox_type", // MAGIC
124 "to_chars", // MBTABLE
130 "attribution", // STRING
135 const char *commands[] = {
142 const char *tests[] = {
143 "%s %s", "%s %s=42", "%s %s?", "%s ?%s", "%s ?%s=42",
144 "%s ?%s?", "%s no%s", "%s no%s=42", "%s no%s?", "%s inv%s",
145 "%s inv%s=42", "%s inv%s?", "%s &%s", "%s &%s=42", "%s &%s?",
148 struct Buffer tmp = mutt_buffer_make(256);
149 struct Buffer err = mutt_buffer_make(256);
152 for (size_t v = 0; v < mutt_array_size(vars); v++)
154 // printf("--------------------------------------------------------------------------------\n");
155 // printf("VARIABLE %s\n", vars[v]);
156 for (size_t c = 0; c < mutt_array_size(commands); c++)
158 // printf("----------------------------------------\n");
159 // printf("COMMAND %s\n", commands[c]);
160 for (size_t t = 0; t < mutt_array_size(tests); t++)
162 mutt_buffer_reset(&tmp);
163 mutt_buffer_reset(&err);
165 snprintf(line, sizeof(line), tests[t], commands[c], vars[v]);
166 printf("%-26s", line);
167 enum CommandResult rc = mutt_parse_rc_line(line, &tmp, &err);
168 printf("%2d %s\n", rc, err.data);
175 mutt_buffer_dealloc(&tmp);
176 mutt_buffer_dealloc(&err);
180 * reset_tilde - Temporary measure
182 static void reset_tilde(struct ConfigSet *cs)
184 static const char *names[] = {
185 "alias_file", "certificate_file", "debug_file",
186 "folder", "history_file", "mbox",
187 "newsrc", "news_cache_dir", "postponed",
188 "record", "signature",
191 struct Buffer value = mutt_buffer_make(256);
192 for (size_t i = 0; i < mutt_array_size(names); i++)
194 struct HashElem *he = cs_get_elem(cs, names[i]);
197 mutt_buffer_reset(&value);
198 cs_he_initial_get(cs, he, &value);
199 mutt_expand_path(value.data, value.dsize);
200 cs_he_initial_set(cs, he, value.data, NULL);
201 cs_he_reset(cs, he, NULL);
203 mutt_buffer_dealloc(&value);
207 * mutt_exit - Leave NeoMutt NOW
208 * @param code Value to return to the calling environment
210 void mutt_exit(int code)
220 * usage - Display NeoMutt command line
222 static void usage(void)
224 puts(mutt_make_version());
226 /* L10N: Try to limit to 80 columns */
228 " neomutt [-Enx] [-e <command>] [-F <config>] [-H <draft>] [-i <include>]\n"
229 " [-b <address>] [-c <address>] [-s <subject>] [-a <file> [...] --]\n"
231 " neomutt [-nx] [-e <command>] [-F <config>] [-b <address>] [-c <address>]\n"
232 " [-s <subject>] [-a <file> [...] --] <address> [...] < message\n"
233 " neomutt [-nRy] [-e <command>] [-F <config>] [-f <mailbox>] [-m <type>]\n"
234 " neomutt [-n] [-e <command>] [-F <config>] -A <alias>\n"
235 " neomutt [-n] [-e <command>] [-F <config>] -B\n"
236 " neomutt [-n] [-e <command>] [-F <config>] -D [-S]\n"
237 " neomutt [-n] [-e <command>] [-F <config>] -d <level> -l <file>\n"
238 " neomutt [-n] [-e <command>] [-F <config>] -G\n"
239 " neomutt [-n] [-e <command>] [-F <config>] -g <server>\n"
240 " neomutt [-n] [-e <command>] [-F <config>] -p\n"
241 " neomutt [-n] [-e <command>] [-F <config>] -Q <variable>\n"
242 " neomutt [-n] [-e <command>] [-F <config>] -Z\n"
243 " neomutt [-n] [-e <command>] [-F <config>] -z [-f <mailbox>]\n"
244 " neomutt -v[v]\n"));
246 /* L10N: Try to limit to 80 columns. If more space is needed add an indented line */
248 " -- Special argument forces NeoMutt to stop option parsing and treat\n"
249 " remaining arguments as addresses even if they start with a dash\n"
250 " -A <alias> Print an expanded version of the given alias to stdout and exit\n"
251 " -a <file> Attach one or more files to a message (must be the last option)\n"
252 " Add any addresses after the '--' argument\n"
253 " -B Run in batch mode (do not start the ncurses UI)\n"
254 " -b <address> Specify a blind carbon copy (Bcc) recipient\n"
255 " -c <address> Specify a carbon copy (Cc) recipient\n"
256 " -D Dump all config variables as 'name=value' pairs to stdout\n"
257 " -D -S Like -D, but hide the value of sensitive variables\n"
258 " -d <level> Log debugging output to a file (default is \"~/.neomuttdebug0\")\n"
259 " The level can range from 1-5 and affects verbosity\n"
260 " -E Edit draft (-H) or include (-i) file during message composition\n"
261 " -e <command> Specify a command to be run after reading the config files\n"
262 " -F <config> Specify an alternative initialization file to read\n"
263 " -f <mailbox> Specify a mailbox (as defined with 'mailboxes' command) to load\n"
264 " -G Start NeoMutt with a listing of subscribed newsgroups\n"
265 " -g <server> Like -G, but start at specified news server\n"
266 " -H <draft> Specify a draft file with header and body for message composing\n"
267 " -h Print this help message and exit\n"
268 " -i <include> Specify an include file to be embedded in the body of a message\n"
269 " -l <file> Specify a file for debugging output (default \"~/.neomuttdebug0\")\n"
270 " -m <type> Specify a default mailbox format type for newly created folders\n"
271 " The type is either MH, MMDF, Maildir or mbox (case-insensitive)\n"
272 " -n Do not read the system-wide configuration file\n"
273 " -p Resume a prior postponed message, if any\n"
274 " -Q <variable> Query a configuration variable and print its value to stdout\n"
275 " (after the config has been read and any commands executed)\n"
276 " -R Open mailbox in read-only mode\n"
277 " -s <subject> Specify a subject (must be enclosed in quotes if it has spaces)\n"
278 " -v Print the NeoMutt version and compile-time definitions and exit\n"
279 " -vv Print the NeoMutt license and copyright information and exit\n"
280 " -x Simulate the mailx(1) send mode\n"
281 " -y Start NeoMutt with a listing of all defined mailboxes\n"
282 " -Z Open the first mailbox with new message or exit immediately with\n"
283 " exit code 1 if none is found in all defined mailboxes\n"
284 " -z Open the first or specified (-f) mailbox if it holds any message\n"
285 " or exit immediately with exit code 1 otherwise"));
290 * start_curses - Start the curses or slang UI
294 static int start_curses(void)
296 km_init(); /* must come before mutt_init */
298 #ifdef USE_SLANG_CURSES
299 SLtt_Ignore_Beep = 1; /* don't do that #*$@^! annoying visual beep! */
300 SLsmg_Display_Eight_Bit = 128; /* characters above this are printable */
301 SLtt_set_color(0, NULL, "default", "default");
302 #if SLANG_VERSION >= 20000
306 /* should come before initscr() so that ncurses 4.2 doesn't try to install
307 * its own SIGWINCH handler */
312 mutt_error(_("Error initializing terminal"));
315 /* slang requires the signal handlers to be set after initializing */
318 keypad(stdscr, true);
322 #ifdef HAVE_TYPEAHEAD
323 typeahead(-1); /* simulate smooth scrolling */
328 init_extended_keys();
329 /* Now that curses is set up, we drop back to normal screen mode.
330 * This simplifies displaying error messages to the user.
331 * The first call to refresh() will swap us back to curses screen mode. */
337 * init_locale - Initialise the Locale/NLS settings
339 static void init_locale(void)
341 setlocale(LC_ALL, "");
344 const char *domdir = mutt_str_getenv("TEXTDOMAINDIR");
346 bindtextdomain(PACKAGE, domdir);
348 bindtextdomain(PACKAGE, MUTTLOCALEDIR);
352 /* Do we have a locale definition? */
353 if (mutt_str_getenv("LC_ALL") || mutt_str_getenv("LANG") || mutt_str_getenv("LC_CTYPE"))
361 * get_user_info - Find the user's name, home and shell
362 * @param cs Config Set
363 * @retval true Success
365 * Find the login name, real name, home directory and shell.
367 bool get_user_info(struct ConfigSet *cs)
369 mutt_str_replace(&Username, mutt_str_getenv("USER"));
370 mutt_str_replace(&HomeDir, mutt_str_getenv("HOME"));
372 const char *shell = mutt_str_getenv("SHELL");
374 cs_str_initial_set(cs, "shell", shell, NULL);
376 /* Get some information about the user */
377 struct passwd *pw = getpwuid(getuid());
381 Username = mutt_str_strdup(pw->pw_name);
383 HomeDir = mutt_str_strdup(pw->pw_dir);
385 cs_str_initial_set(cs, "shell", pw->pw_shell, NULL);
390 mutt_error(_("unable to determine username"));
391 return false; // TEST05: neomutt (unset $USER, delete user from /etc/passwd)
396 mutt_error(_("unable to determine home directory"));
397 return false; // TEST06: neomutt (unset $HOME, delete user from /etc/passwd)
400 cs_str_reset(cs, "shell", NULL);
405 * main - Start NeoMutt
406 * @param argc Number of command line arguments
407 * @param argv List of command line arguments
408 * @param envp Copy of the environment
412 int main(int argc, char *argv[], char *envp[])
414 char *subject = NULL;
415 char *include_file = NULL;
416 char *draft_file = NULL;
417 char *new_magic = NULL;
421 char *cli_nntp = NULL;
423 struct Email *e = NULL;
424 struct ListHead attach = STAILQ_HEAD_INITIALIZER(attach);
425 struct ListHead commands = STAILQ_HEAD_INITIALIZER(commands);
426 struct ListHead queries = STAILQ_HEAD_INITIALIZER(queries);
427 struct ListHead alias_queries = STAILQ_HEAD_INITIALIZER(alias_queries);
428 struct ListHead cc_list = STAILQ_HEAD_INITIALIZER(cc_list);
429 struct ListHead bcc_list = STAILQ_HEAD_INITIALIZER(bcc_list);
430 SendFlags sendflags = SEND_NO_FLAGS;
431 CliFlags flags = MUTT_CLI_NO_FLAGS;
434 bool explicit_folder = false;
435 bool dump_variables = false;
436 bool hide_sensitive = false;
437 bool batch_mode = false;
438 bool edit_infile = false;
439 bool test_config = false;
442 int double_dash = argc, nargc = 1;
444 bool repeat_error = false;
445 struct Buffer folder = mutt_buffer_make(0);
447 MuttLogger = log_disp_terminal;
449 /* sanity check against stupid administrators */
450 if (getegid() != getgid())
452 mutt_error("%s: I don't want to run with privileges!", argv[0]);
453 goto main_exit; // TEST01: neomutt (as root, chgrp mail neomutt; chmod +s neomutt)
459 if (mutt_randbuf(&out, sizeof(out)) < 0)
460 goto main_exit; // TEST02: neomutt (as root on non-Linux OS, rename /dev/urandom)
464 mutt_envlist_init(envp);
465 for (optind = 1; optind < double_dash;)
467 /* We're getopt'ing POSIXLY, so we'll be here every time getopt()
468 * encounters a non-option. That could be a file to attach
469 * (all non-options between -a and --) or it could be an address
470 * (which gets collapsed to the front of argv). */
471 for (; optind < argc; optind++)
473 if ((argv[optind][0] == '-') && (argv[optind][1] != '\0'))
475 if ((argv[optind][1] == '-') && (argv[optind][2] == '\0'))
476 double_dash = optind; /* quit outer loop after getopt */
477 break; /* drop through to getopt */
480 /* non-option, either an attachment or address */
481 if (!STAILQ_EMPTY(&attach))
482 mutt_list_insert_tail(&attach, mutt_str_strdup(argv[optind]));
484 argv[nargc++] = argv[optind];
488 i = getopt(argc, argv, "+A:a:Bb:F:f:c:Dd:l:Ee:g:GH:i:hm:npQ:RSs:TvxyzZ");
494 mutt_list_insert_tail(&alias_queries, mutt_str_strdup(optarg));
497 mutt_list_insert_tail(&attach, mutt_str_strdup(optarg));
503 mutt_list_insert_tail(&bcc_list, mutt_str_strdup(optarg));
506 mutt_list_insert_tail(&cc_list, mutt_str_strdup(optarg));
509 dump_variables = true;
518 mutt_list_insert_tail(&commands, mutt_str_strdup(optarg));
521 mutt_list_insert_tail(&Muttrc, mutt_str_strdup(optarg));
524 mutt_buffer_strcpy(&folder, optarg);
525 explicit_folder = true;
528 case 'g': /* Specify a news server */
531 case 'G': /* List of newsgroups */
532 flags |= MUTT_CLI_SELECT | MUTT_CLI_NEWS;
539 include_file = optarg;
548 flags |= MUTT_CLI_NOSYSRC;
551 sendflags |= SEND_POSTPONED;
554 mutt_list_insert_tail(&queries, mutt_str_strdup(optarg));
557 flags |= MUTT_CLI_RO; /* read-only mode */
560 hide_sensitive = true;
571 case 'x': /* mailx compatible send mode */
572 sendflags |= SEND_MAILX;
574 case 'y': /* My special hack mode */
575 flags |= MUTT_CLI_SELECT;
578 flags |= MUTT_CLI_MAILBOX | MUTT_CLI_IGNORE;
581 flags |= MUTT_CLI_IGNORE;
586 goto main_ok; // TEST03: neomutt -9
591 /* collapse remaining argv */
592 while (optind < argc)
593 argv[nargc++] = argv[optind++];
599 log_queue_flush(log_disp_terminal);
601 print_version(stdout);
605 goto main_ok; // TEST04: neomutt -v
608 Config = init_config(500);
611 NeoMutt = neomutt_new(Config);
613 notify_set_parent(Config->notify, NeoMutt->notify);
615 if (!get_user_info(Config))
620 cs_str_initial_set(Config, "from", "rich@flatcap.org", NULL);
621 cs_str_reset(Config, "from", NULL);
622 myvar_set("my_var", "foo");
631 cs_str_initial_set(Config, "debug_file", dfile, NULL);
632 cs_str_reset(Config, "debug_file", NULL);
638 if ((mutt_str_atos(dlevel, &num) < 0) || (num < LL_MESSAGE) || (num >= LL_MAX))
640 mutt_error(_("Error: value '%s' is invalid for -d"), dlevel);
641 goto main_exit; // TEST07: neomutt -d xyz
643 cs_str_initial_set(Config, "debug_level", dlevel, NULL);
644 cs_str_reset(Config, "debug_level", NULL);
651 MuttLogger = log_disp_queue;
653 if (!STAILQ_EMPTY(&cc_list) || !STAILQ_EMPTY(&bcc_list))
656 e->env = mutt_env_new();
658 struct ListNode *np = NULL;
659 STAILQ_FOREACH(np, &bcc_list, entries)
661 mutt_addrlist_parse(&e->env->bcc, np->data);
664 STAILQ_FOREACH(np, &cc_list, entries)
666 mutt_addrlist_parse(&e->env->cc, np->data);
669 mutt_list_free(&bcc_list);
670 mutt_list_free(&cc_list);
673 /* Check for a batch send. */
674 if (!isatty(0) || !STAILQ_EMPTY(&queries) || !STAILQ_EMPTY(&alias_queries) ||
675 dump_variables || batch_mode)
678 sendflags = SEND_BATCH;
679 MuttLogger = log_disp_terminal;
680 log_queue_flush(log_disp_terminal);
683 /* Always create the mutt_windows because batch mode has some shared code
684 * paths that end up referencing them. */
687 /* This must come before mutt_init() because curses needs to be started
688 * before calling the init_pair() function to set the color scheme. */
691 int crc = start_curses();
694 goto main_curses; // TEST08: can't test -- fake term?
696 /* check whether terminal status is supported (must follow curses init) */
697 TsSupported = mutt_ts_capability();
698 mutt_window_reflow();
701 /* set defaults and read init files */
702 if (mutt_init(flags & MUTT_CLI_NOSYSRC, &commands) != 0)
705 /* The command line overrides the config */
707 cs_str_reset(Config, "debug_level", NULL);
709 cs_str_reset(Config, "debug_file", NULL);
711 if (mutt_log_start() < 0)
713 mutt_perror("log file");
717 mutt_list_free(&commands);
720 /* "$news_server" precedence: command line, config file, environment, system file */
722 cs_str_string_set(Config, "news_server", cli_nntp, NULL);
725 const char *env_nntp = mutt_str_getenv("NNTPSERVER");
726 cs_str_string_set(Config, "news_server", env_nntp, NULL);
731 char *server = mutt_file_read_keyword(SYSCONFDIR "/nntpserver", buf, sizeof(buf));
732 cs_str_string_set(Config, "news_server", server, NULL);
735 cs_str_initial_set(Config, "news_server", C_NewsServer, NULL);
738 /* Initialize crypto backends. */
743 struct Buffer err = mutt_buffer_make(0);
744 int r = cs_str_initial_set(Config, "mbox_type", new_magic, &err);
745 if (CSR_RESULT(r) != CSR_SUCCESS)
747 mutt_error(err.data);
748 mutt_buffer_dealloc(&err);
751 cs_str_reset(Config, "mbox_type", NULL);
754 if (!STAILQ_EMPTY(&queries))
756 rc = mutt_query_variables(&queries);
762 dump_config(Config, hide_sensitive ? CS_DUMP_HIDE_SENSITIVE : CS_DUMP_NO_FLAGS, stdout);
763 goto main_ok; // TEST18: neomutt -D
766 if (!STAILQ_EMPTY(&alias_queries))
769 for (; optind < argc; optind++)
770 mutt_list_insert_tail(&alias_queries, mutt_str_strdup(argv[optind]));
771 struct ListNode *np = NULL;
772 STAILQ_FOREACH(np, &alias_queries, entries)
774 struct AddressList *al = mutt_alias_lookup(np->data);
777 /* output in machine-readable form */
778 mutt_addrlist_to_intl(al, NULL);
779 mutt_write_addrlist(al, stdout, 0, 0);
784 printf("%s\n", np->data); // TEST19: neomutt -A unknown
787 mutt_list_free(&alias_queries);
788 goto main_curses; // TEST20: neomutt -A alias
793 mutt_curses_set_color(MT_COLOR_NORMAL);
795 MuttLogger = log_disp_curses;
796 log_queue_flush(log_disp_curses);
797 log_queue_set_max_size(100);
800 /* Initialize autocrypt after curses messages are working,
801 * because of the initial account setup screens. */
804 mutt_autocrypt_init(!(sendflags & SEND_BATCH));
807 /* Create the C_Folder directory if it doesn't exist. */
808 if (!OptNoCurses && C_Folder)
811 char fpath[PATH_MAX];
813 mutt_str_strfcpy(fpath, C_Folder, sizeof(fpath));
814 mutt_expand_path(fpath, sizeof(fpath));
817 /* we're not connected yet - skip mail folder creation */
818 skip |= (imap_path_probe(fpath, NULL) == MUTT_IMAP);
821 skip |= (nntp_path_probe(fpath, NULL) == MUTT_NNTP);
823 if (!skip && (stat(fpath, &sb) == -1) && (errno == ENOENT))
826 snprintf(msg2, sizeof(msg2), _("%s does not exist. Create it?"), C_Folder);
827 if (mutt_yesorno(msg2, MUTT_YES) == MUTT_YES)
829 if ((mkdir(fpath, 0700) == -1) && (errno != EEXIST))
830 mutt_error(_("Can't create %s: %s"), C_Folder, strerror(errno)); // TEST21: neomutt -n -F /dev/null (and ~/Mail doesn't exist)
837 goto main_ok; // TEST22: neomutt -B
840 notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_hist_observer, 0);
841 notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_log_observer, 0);
842 notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_menu_observer, 0);
843 notify_observer_add(Config->notify, NT_CONFIG, 0, mutt_reply_observer, 0);
845 if (sendflags & SEND_POSTPONED)
849 if (ci_send_message(SEND_POSTPONED, NULL, NULL, NULL, NULL) == 0)
851 // TEST23: neomutt -p (postponed message, cancel)
852 // TEST24: neomutt -p (no postponed message)
857 else if (subject || e || sendflags || draft_file || include_file ||
858 !STAILQ_EMPTY(&attach) || (optind < argc))
862 char *tempfile = NULL, *infile = NULL;
863 char *bodytext = NULL, *bodyfile = NULL;
865 char expanded_infile[PATH_MAX];
873 e->env = mutt_env_new();
875 for (i = optind; i < argc; i++)
877 if (url_check_scheme(argv[i]) == U_MAILTO)
879 if (mutt_parse_mailto(e->env, &bodytext, argv[i]) < 0)
881 mutt_error(_("Failed to parse mailto: link"));
882 goto main_curses; // TEST25: neomutt mailto:
886 mutt_addrlist_parse(&e->env->to, argv[i]);
889 if (!draft_file && C_Autoedit && TAILQ_EMPTY(&e->env->to) &&
890 TAILQ_EMPTY(&e->env->cc))
892 mutt_error(_("No recipients specified"));
893 goto main_curses; // TEST26: neomutt -s test (with autoedit=yes)
897 e->env->subject = mutt_str_strdup(subject);
904 else if (include_file)
905 infile = include_file;
909 if (infile || bodytext)
911 /* Prepare fp_in and expanded_infile. */
914 if (mutt_str_strcmp("-", infile) == 0)
918 mutt_error(_("Can't use -E flag with stdin"));
919 goto main_curses; // TEST27: neomutt -E -H -
925 mutt_str_strfcpy(expanded_infile, infile, sizeof(expanded_infile));
926 mutt_expand_path(expanded_infile, sizeof(expanded_infile));
927 fp_in = fopen(expanded_infile, "r");
930 mutt_perror(expanded_infile);
931 goto main_curses; // TEST28: neomutt -E -H missing
936 /* Copy input to a tempfile, and re-point fp_in to the tempfile.
937 * Note: stdin is always copied to a tempfile, ensuring draft_file
938 * can stat and get the correct st_size below. */
942 mutt_mktemp(buf, sizeof(buf));
943 tempfile = mutt_str_strdup(buf);
945 fp_out = mutt_file_fopen(tempfile, "w");
948 mutt_file_fclose(&fp_in);
949 mutt_perror(tempfile);
951 goto main_curses; // TEST29: neomutt -H existing-file (where tmpdir=/path/to/FILE blocking tmpdir)
955 mutt_file_copy_stream(fp_in, fp_out);
957 mutt_file_fclose(&fp_in);
960 fputs(bodytext, fp_out);
961 mutt_file_fclose(&fp_out);
963 fp_in = fopen(tempfile, "r");
966 mutt_perror(tempfile);
968 goto main_curses; // TEST30: can't test
971 /* If editing the infile, keep it around afterwards so
972 * it doesn't get unlinked, and we can rebuild the draft_file */
974 sendflags |= SEND_NO_FREE_HEADER;
976 /* Parse the draft_file into the full Email/Body structure.
977 * Set SEND_DRAFT_FILE so ci_send_message doesn't overwrite
981 struct Envelope *opts_env = e->env;
984 sendflags |= SEND_DRAFT_FILE;
986 /* Set up a tmp Email with just enough information so that
987 * mutt_prepare_template() can parse the message in fp_in. */
988 struct Email *e_tmp = email_new();
990 e_tmp->content = mutt_body_new();
991 if (fstat(fileno(fp_in), &st) != 0)
993 mutt_perror(draft_file);
994 goto main_curses; // TEST31: can't test
996 e_tmp->content->length = st.st_size;
998 if (mutt_prepare_template(fp_in, NULL, e, e_tmp, false) < 0)
1000 mutt_error(_("Can't parse message template: %s"), draft_file);
1001 mutt_env_free(&opts_env);
1006 /* Scan for neomutt header to set C_ResumeDraftFiles */
1007 struct ListNode *np = NULL, *tmp = NULL;
1008 STAILQ_FOREACH_SAFE(np, &e->env->userhdrs, entries, tmp)
1010 if (mutt_str_startswith(np->data, "X-Mutt-Resume-Draft:", CASE_IGNORE))
1012 if (C_ResumeEditedDraftFiles)
1013 cs_str_native_set(Config, "resume_draft_files", true, NULL);
1015 STAILQ_REMOVE(&e->env->userhdrs, np, ListNode, entries);
1021 mutt_addrlist_copy(&e->env->to, &opts_env->to, false);
1022 mutt_addrlist_copy(&e->env->cc, &opts_env->cc, false);
1023 mutt_addrlist_copy(&e->env->bcc, &opts_env->bcc, false);
1024 if (opts_env->subject)
1025 mutt_str_replace(&e->env->subject, opts_env->subject);
1027 mutt_env_free(&opts_env);
1030 /* Editing the include_file: pass it directly in.
1031 * Note that SEND_NO_FREE_HEADER is set above so it isn't unlinked. */
1032 else if (edit_infile)
1033 bodyfile = expanded_infile;
1034 /* For bodytext and unedited include_file: use the tempfile.
1037 bodyfile = tempfile;
1039 mutt_file_fclose(&fp_in);
1044 if (!STAILQ_EMPTY(&attach))
1046 struct Body *b = e->content;
1048 while (b && b->next)
1051 struct ListNode *np = NULL;
1052 STAILQ_FOREACH(np, &attach, entries)
1056 b->next = mutt_make_file_attach(np->data);
1061 b = mutt_make_file_attach(np->data);
1066 mutt_error(_("%s: unable to attach file"), np->data);
1067 mutt_list_free(&attach);
1068 goto main_curses; // TEST32: neomutt john@example.com -a missing
1071 mutt_list_free(&attach);
1074 rv = ci_send_message(sendflags, e, bodyfile, NULL, NULL);
1075 /* We WANT the "Mail sent." and any possible, later error */
1077 if (ErrorBufMessage)
1078 mutt_message("%s", ErrorBuf);
1083 e->content->unlink = false;
1084 else if (draft_file)
1086 if (truncate(expanded_infile, 0) == -1)
1088 mutt_perror(expanded_infile);
1089 goto main_curses; // TEST33: neomutt -H read-only -s test john@example.com -E
1091 fp_out = mutt_file_fopen(expanded_infile, "a");
1094 mutt_perror(expanded_infile);
1095 goto main_curses; // TEST34: can't test
1098 /* If the message was sent or postponed, these will already
1099 * have been done. */
1102 if (e->content->next)
1103 e->content = mutt_make_multipart(e->content);
1104 mutt_encode_descriptions(e->content, true);
1105 mutt_prepare_envelope(e->env, false);
1106 mutt_env_to_intl(e->env, NULL, NULL);
1109 mutt_rfc822_write_header(
1110 fp_out, e->env, e->content, MUTT_WRITE_HEADER_POSTPONE, false,
1111 C_CryptProtectedHeadersRead && mutt_should_hide_protected_subject(e));
1112 if (C_ResumeEditedDraftFiles)
1113 fprintf(fp_out, "X-Mutt-Resume-Draft: 1\n");
1114 fputc('\n', fp_out);
1115 if ((mutt_write_mime_body(e->content, fp_out) == -1))
1117 mutt_file_fclose(&fp_out);
1118 goto main_curses; // TEST35: can't test
1120 mutt_file_fclose(&fp_out);
1126 /* !edit_infile && draft_file will leave the tempfile around */
1133 mutt_window_free_all();
1136 goto main_curses; // TEST36: neomutt -H existing -s test john@example.com -E (cancel sending)
1140 if (flags & MUTT_CLI_MAILBOX)
1143 bool passive = C_ImapPassive;
1144 C_ImapPassive = false;
1146 if (mutt_mailbox_check(Context ? Context->mailbox : NULL, 0) == 0)
1148 mutt_message(_("No mailbox with new mail"));
1149 goto main_curses; // TEST37: neomutt -Z (no new mail)
1151 mutt_buffer_reset(&folder);
1152 mutt_mailbox_next_buffer(Context ? Context->mailbox : NULL, &folder);
1154 C_ImapPassive = passive;
1157 else if (flags & MUTT_CLI_SELECT)
1160 if (flags & MUTT_CLI_NEWS)
1164 nntp_select_server(Context ? Context->mailbox : NULL, C_NewsServer, false);
1165 if (!CurrentNewsSrv)
1166 goto main_curses; // TEST38: neomutt -G (unset news_server)
1170 if (TAILQ_EMPTY(&NeoMutt->accounts))
1172 mutt_error(_("No incoming mailboxes defined"));
1173 goto main_curses; // TEST39: neomutt -n -F /dev/null -y
1175 mutt_buffer_reset(&folder);
1176 mutt_buffer_select_file(&folder, MUTT_SEL_FOLDER | MUTT_SEL_MAILBOX, NULL, NULL);
1177 if (mutt_buffer_is_empty(&folder))
1179 goto main_ok; // TEST40: neomutt -y (quit selection)
1183 if (mutt_buffer_is_empty(&folder))
1187 // Check if C_Spoolfile corresponds a mailboxes' description.
1188 struct Mailbox *m_desc = mailbox_find_name(C_Spoolfile);
1190 mutt_buffer_strcpy(&folder, m_desc->realpath);
1192 mutt_buffer_strcpy(&folder, C_Spoolfile);
1195 mutt_buffer_strcpy(&folder, C_Folder);
1196 /* else no folder */
1203 mutt_buffer_alloc(&folder, PATH_MAX);
1204 nntp_expand_path(folder.data, folder.dsize, &CurrentNewsSrv->conn->account);
1208 mutt_buffer_expand_path(&folder);
1210 mutt_str_replace(&CurrentFolder, mutt_b2s(&folder));
1211 mutt_str_replace(&LastFolder, mutt_b2s(&folder));
1213 if (flags & MUTT_CLI_IGNORE)
1215 /* check to see if there are any messages in the folder */
1216 switch (mx_check_empty(mutt_b2s(&folder)))
1219 mutt_perror(mutt_b2s(&folder));
1220 goto main_curses; // TEST41: neomutt -z -f missing
1222 mutt_error(_("Mailbox is empty"));
1223 goto main_curses; // TEST42: neomutt -z -f /dev/null
1227 mutt_folder_hook(mutt_b2s(&folder), NULL);
1228 mutt_startup_shutdown_hook(MUTT_STARTUP_HOOK);
1229 notify_send(NeoMutt->notify, NT_GLOBAL, NT_GLOBAL_STARTUP, 0);
1231 repeat_error = true;
1232 struct Mailbox *m = mx_path_resolve(mutt_b2s(&folder));
1233 Context = mx_mbox_open(m, ((flags & MUTT_CLI_RO) || C_ReadOnly) ? MUTT_READONLY : MUTT_OPEN_NO_FLAGS);
1238 account_mailbox_remove(m->account, m);
1244 if (Context || !explicit_folder)
1247 mutt_sb_set_open_mailbox(Context ? Context->mailbox : NULL);
1258 #ifdef USE_AUTOCRYPT
1259 mutt_autocrypt_cleanup();
1263 // TEST43: neomutt (no change to mailbox)
1264 // TEST44: neomutt (change mailbox)
1273 log_queue_flush(log_disp_terminal);
1274 mutt_unlink_temp_attachments();
1276 /* Repeat the last message to the user */
1277 if (repeat_error && ErrorBufMessage)
1280 mutt_buffer_dealloc(&folder);
1281 mutt_list_free(&queries);
1282 crypto_module_free();
1283 mutt_window_free_all();
1284 mutt_buffer_pool_free();
1285 mutt_envlist_free();
1286 mutt_browser_cleanup();
1289 myvarlist_free(&MyVars);
1290 neomutt_free(&NeoMutt);