1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2002-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 /* MH repl command */
18 
19 #include <mh.h>
20 #include <mh_format.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 
24 static char prog_doc[] = N_("Reply to a message");
25 static char args_doc[] = N_("[MESSAGE]");
26 
27 static mh_format_t format;
28 static mh_fvm_t fvm;
29 static int width;
30 
31 struct mh_whatnow_env wh_env = { 0 };
32 static int initial_edit = 1;
33 static const char *whatnowproc;
34 static mu_msgset_t msgset;
35 static mu_mailbox_t mbox;
36 static int build_only = 0; /* -build flag */
37 static int use_draft = 0;  /* -use flag */
38 static char *mhl_filter = NULL; /* -filter flag */
39 static int annotate;       /* -annotate flag */
40 static char *draftmessage = "new";
41 static const char *draftfolder = NULL;
42 static mu_opool_t fcc_pool;
43 static int has_fcc;
44 
45 static int
decode_cc_flag(const char * opt,const char * arg)46 decode_cc_flag (const char *opt, const char *arg)
47 {
48   int rc = mh_decode_rcpt_flag (arg);
49   if (rc == RCPT_NONE)
50     {
51       mu_error (_("%s %s is unknown"), opt, arg);
52       exit (1);
53     }
54   return rc;
55 }
56 
57 static void
set_cc(struct mu_parseopt * po,struct mu_option * opt,char const * arg)58 set_cc (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
59 {
60   rcpt_mask |= decode_cc_flag ("-cc", arg);
61 }
62 
63 static void
clr_cc(struct mu_parseopt * po,struct mu_option * opt,char const * arg)64 clr_cc (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
65 {
66   rcpt_mask &= ~decode_cc_flag ("-nocc", arg);
67 }
68 
69 static void
set_fcc(struct mu_parseopt * po,struct mu_option * opt,char const * arg)70 set_fcc (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
71 {
72   if (!has_fcc)
73     {
74       mu_opool_create (&fcc_pool, MU_OPOOL_ENOMEMABRT);
75       has_fcc = 1;
76     }
77   else
78     mu_opool_append (fcc_pool, ", ", 2);
79   mu_opool_appendz (fcc_pool, arg);
80 }
81 
82 static void
set_whatnowproc(struct mu_parseopt * po,struct mu_option * opt,char const * arg)83 set_whatnowproc (struct mu_parseopt *po, struct mu_option *opt,
84 		 char const *arg)
85 {
86   whatnowproc = mu_strdup (arg);
87   wh_env.nowhatnowproc = 0;
88 }
89 
90 static void
set_group(struct mu_parseopt * po,struct mu_option * opt,char const * arg)91 set_group (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
92 {
93   if (strcmp (arg, "1") == 0)
94     {
95       if (!format && mh_format_file_parse (&format, "replgroupcomps",
96 					   MH_FMT_PARSE_DEFAULT))
97 	exit (1);
98       rcpt_mask |= RCPT_ALL;
99     }
100 }
101 
102 static struct mu_option options[] = {
103   { "annotate", 0, NULL, MU_OPTION_DEFAULT,
104     N_("add Replied: header to the message being replied to"),
105     mu_c_bool, &annotate },
106   { "build",    0, NULL, MU_OPTION_DEFAULT,
107     N_("build the draft and quit immediately"),
108     mu_c_bool, &build_only },
109   { "draftfolder", 0, N_("FOLDER"), MU_OPTION_DEFAULT,
110     N_("specify the folder for message drafts"),
111     mu_c_string, &draftfolder },
112   { "nodraftfolder", 0, NULL, MU_OPTION_DEFAULT,
113     N_("undo the effect of the last --draftfolder option"),
114     mu_c_string, &draftfolder, mh_opt_clear_string },
115   { "draftmessage" , 0, N_("MSG"), MU_OPTION_DEFAULT,
116     N_("invoke the draftmessage facility"),
117     mu_c_string, &draftmessage },
118   { "cc", 0, "{all|to|cc|me}", 0,
119     N_("specify whom to place on the Cc: list of the reply"),
120     mu_c_string, NULL, set_cc },
121   { "nocc", 0, "{all|to|cc|me}", 0,
122     N_("specify whom to remove from the Cc: list of the reply"),
123     mu_c_string, NULL, clr_cc },
124   { "group", 0,  NULL, MU_OPTION_DEFAULT,
125     N_("construct a group or followup reply"),
126     mu_c_bool, NULL, set_group },
127   { "editor", 0, N_("PROG"), MU_OPTION_DEFAULT,
128     N_("set the editor program to use"),
129     mu_c_string, &wh_env.editor },
130   { "noedit", 0, NULL, MU_OPTION_DEFAULT,
131     N_("suppress the initial edit"),
132     mu_c_int, &initial_edit, NULL, "0" },
133   { "fcc",    0, N_("FOLDER"), MU_OPTION_DEFAULT,
134     N_("set the folder to receive Fcc's"),
135     mu_c_string, NULL, set_fcc },
136   { "filter", 0, N_("MHL-FILTER"), MU_OPTION_DEFAULT,
137     N_("set the mhl filter to preprocess the body of the message being replied"),
138     mu_c_string, &mhl_filter, mh_opt_find_file },
139   { "form",    0, N_("FILE"),   MU_OPTION_DEFAULT,
140     N_("read format from given file"),
141     mu_c_string, &format, mh_opt_parse_formfile },
142   { "format", 0, NULL, MU_OPTION_DEFAULT,
143     N_("include a copy of the message being replied; the message will be processed using either the default filter \"mhl.reply\", or the filter specified by --filter option"),
144     mu_c_string, &mhl_filter, mh_opt_find_file, "mhl.repl" },
145   { "noformat", 0, NULL,   MU_OPTION_DEFAULT,
146     N_("cancels the effect of the recent -format option"),
147     mu_c_string, &mhl_filter, mh_opt_clear_string },
148   { "inplace",  0, NULL,   MU_OPTION_HIDDEN,
149     N_("annotate the message in place"),
150     mu_c_bool, NULL, mh_opt_notimpl_warning },
151   { "query",    0, NULL,   MU_OPTION_HIDDEN,
152     N_("query for addresses to place in To: and Cc: lists"),
153     mu_c_bool, NULL, mh_opt_notimpl_warning },
154   { "width",    0, N_("NUMBER"), MU_OPTION_DEFAULT,
155     N_("set output width"),
156     mu_c_int, &width },
157   { "whatnowproc", 0, N_("PROG"), MU_OPTION_DEFAULT,
158     N_("set the replacement for whatnow program"),
159     mu_c_string, NULL, set_whatnowproc },
160   /* TRANSLATORS: "whatnowproc" is the variable name */
161   { "nowhatnowproc", 0, NULL, MU_OPTION_DEFAULT,
162     N_("don't run whatnowproc"),
163     mu_c_int, &wh_env.nowhatnowproc, NULL, "1" },
164   { "use", 0, NULL, MU_OPTION_DEFAULT,
165     N_("use draft file preserved after the last session"),
166     mu_c_bool, &use_draft },
167 
168   MU_OPTION_END
169 };
170 
171 static char default_format_str[] =
172     "%(lit)%(formataddr %<{reply-to}%?{from}%?{sender}%?{return-path}%>)"
173     "%<(nonnull)%(void(width))%(putaddr To: )\\n%>"
174     "%(lit)%<(rcpt to)%(formataddr{to})%>%<(rcpt cc)%(formataddr{cc})%>%<(rcpt me)%(formataddr(me))%>"
175     "%<(nonnull)%(void(width))%(putaddr cc: )\\n%>"
176     "%<(mymbox{from})%<{fcc}Fcc: %{fcc}\\n%>%>"
177     "Subject: %<{subject}%(putstr %<(profile reply-prefix)%|"
178     "%(void Re:)%>) %(void(unre{subject}))%(trim)%(putstr)%>\n"
179     "%(lit)%<(in_reply_to)%(void(width))%(printhdr In-reply-to: )\\n%>"
180     "%(lit)%<(references)%(void(width))%(printhdr References: )\\n%>"
181     "User-Agent: MH (%(package_string))\n"
182     "--------\n";
183 
184 void
make_draft(mu_mailbox_t mbox,int disp,struct mh_whatnow_env * wh)185 make_draft (mu_mailbox_t mbox, int disp, struct mh_whatnow_env *wh)
186 {
187   int rc;
188   mu_message_t msg;
189   size_t msgno;
190 
191   /* First check if the draft exists */
192   if (!build_only)
193     disp = check_draft_disposition (wh, use_draft);
194 
195   switch (disp)
196     {
197     case DISP_QUIT:
198       exit (0);
199 
200     case DISP_USE:
201       break;
202 
203     case DISP_REPLACE:
204       unlink (wh->draftfile);
205       break;
206     }
207 
208   msgno = mh_msgset_first (msgset, RET_MSGNO);
209   rc = mu_mailbox_get_message (mbox, msgno, &msg);
210   if (rc)
211     {
212       mu_error (_("cannot read message %s: %s"),
213 		mu_umaxtostr (0, msgno),
214 		mu_strerror (rc));
215       exit (1);
216     }
217   if (annotate)
218     {
219       wh->anno_field = "Replied";
220       mu_list_create (&wh->anno_list);
221       mu_list_append (wh->anno_list, msg);
222     }
223 
224   if (disp == DISP_REPLACE)
225     {
226       mu_stream_t str;
227 
228       rc = mu_file_stream_create (&str, wh->file,
229 				  MU_STREAM_WRITE|MU_STREAM_CREAT);
230       if (rc)
231 	{
232 	  mu_error (_("cannot create draft file stream %s: %s"),
233 		    wh->file, mu_strerror (rc));
234 	  exit (1);
235 	}
236 
237       mh_fvm_set_output (fvm, str);
238 
239       if (has_fcc)
240 	{
241 	  mu_message_t tmp_msg;
242 	  mu_header_t hdr;
243 	  char *text;
244 
245 	  mu_message_create_copy (&tmp_msg, msg);
246 	  mu_message_get_header (tmp_msg, &hdr);
247 	  text = mu_opool_finish (fcc_pool, NULL);
248 	  mu_header_set_value (hdr, MU_HEADER_FCC, text, 1);
249 	  mh_fvm_run (fvm, tmp_msg);
250 	  mu_message_destroy (&tmp_msg, NULL);
251 	}
252       else
253 	mh_fvm_run (fvm, msg);
254 
255       if (mhl_filter)
256 	{
257 	  mu_list_t filter = mhl_format_compile (mhl_filter);
258 	  if (!filter)
259 	    exit (1);
260 	  mhl_format_run (filter, width, 0, 0, msg, str);
261 	  mhl_format_destroy (&filter);
262 	}
263 
264       mh_fvm_set_output (fvm, mu_strout);
265       mu_stream_destroy (&str);
266     }
267 
268   {
269     mu_url_t url;
270     size_t num;
271     char *msgname, *p;
272 
273     mu_mailbox_get_url (mbox, &url);
274     mh_message_number (msg, &num);
275     msgname = mh_safe_make_file_name (mu_url_to_string (url),
276                                       mu_umaxtostr (0, num));
277     p = strchr (msgname, ':');
278     if (!p)
279       wh->msg = msgname;
280     else
281       {
282 	wh->msg = mu_strdup (p+1);
283 	free (msgname);
284       }
285   }
286 }
287 
288 static struct mh_optinit optinit[] = {
289   { "draftfolder", "Draft-Folder" },
290   { "whatnowproc", "whatnowproc" },
291   { NULL }
292 };
293 
294 int
main(int argc,char ** argv)295 main (int argc, char **argv)
296 {
297   int rc;
298 
299   mh_getopt_ext (&argc, &argv, options, MH_GETOPT_DEFAULT_FOLDER, optinit,
300 		 args_doc, prog_doc, NULL);
301   if (!format)
302     {
303       if (mh_format_string_parse (&format, default_format_str,
304 				  NULL, MH_FMT_PARSE_DEFAULT))
305 	{
306 	  mu_error (_("INTERNAL ERROR: bad built-in format; please report"));
307 	  exit (1);
308 	}
309     }
310 
311   mh_fvm_create (&fvm, 0);
312   mh_fvm_set_format (fvm, format);
313   mh_fvm_set_width (fvm, width ? width : mh_width ());
314   mh_format_destroy (&format);
315 
316   mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR);
317   mh_msgset_parse (&msgset, mbox, argc, argv, "cur");
318   if (!mh_msgset_single_message (msgset))
319     {
320       mu_error (_("only one message at a time!"));
321       return 1;
322     }
323 
324   if (build_only)
325     wh_env.file = mh_expand_name (draftfolder, "reply", NAME_ANY);
326   else if (draftfolder)
327     {
328       if (mh_draft_message (draftfolder, draftmessage, &wh_env.file))
329 	return 1;
330     }
331   else
332     wh_env.file = mh_expand_name (draftfolder, "draft", NAME_ANY);
333   wh_env.draftfile = wh_env.file;
334 
335   make_draft (mbox, DISP_REPLACE, &wh_env);
336 
337   /* Exit immediately if --build is given */
338   if (build_only || wh_env.nowhatnowproc)
339     return 0;
340 
341   rc = mh_whatnowproc (&wh_env, initial_edit, whatnowproc);
342 
343   mu_mailbox_sync (mbox);
344   mu_mailbox_close (mbox);
345   mu_mailbox_destroy (&mbox);
346   return rc;
347 }
348