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