1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-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 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include "mh.h"
25 #include <mailutils/stream.h>
26 #include <mailutils/wordsplit.h>
27 #include <mailutils/io.h>
28 #include <mailutils/cli.h>
29 #include <mailutils/gitinfo.h>
30 
31 struct getopt_data
32 {
33   char *extra_doc;
34 };
35 
36 static void
mh_extra_help_hook(struct mu_parseopt * po,mu_stream_t stream)37 mh_extra_help_hook (struct mu_parseopt *po, mu_stream_t stream)
38 {
39   struct getopt_data *data = po->po_data;
40   char const *extra_doc = gettext (data->extra_doc);
41   while (*extra_doc)
42     {
43       size_t len = strcspn (extra_doc, "\n");
44       unsigned margin = strspn (extra_doc, " \t");
45 
46       mu_stream_ioctl (stream, MU_IOCTL_WORDWRAPSTREAM,
47 		       MU_IOCTL_WORDWRAP_SET_MARGIN, &margin);
48       extra_doc += margin;
49       len -= margin;
50       mu_stream_write (stream, extra_doc, len, NULL);
51       mu_stream_write (stream, "\n", 1, NULL);
52       extra_doc += len;
53       if (*extra_doc)
54 	extra_doc++;
55     }
56 }
57 
58 static void
augment_argv(int * pargc,char *** pargv)59 augment_argv (int *pargc, char ***pargv)
60 {
61   int argc;
62   char **argv;
63   int i, j;
64   struct mu_wordsplit ws;
65   char const *val = mh_global_profile_get (mu_program_name, NULL);
66 
67   if (!val)
68     return;
69 
70   if (mu_wordsplit (val, &ws, MU_WRDSF_DEFFLAGS))
71     {
72       mu_error (_("cannot split line `%s': %s"), val,
73 		mu_wordsplit_strerror (&ws));
74       exit (1);
75     }
76 
77   argc = *pargc + ws.ws_wordc;
78   argv = calloc (argc + 1, sizeof *argv);
79   if (!argv)
80     mh_err_memory (1);
81 
82   i = 0;
83   argv[i++] = (*pargv)[0];
84   for (j = 0; j < ws.ws_wordc; i++, j++)
85     argv[i] = ws.ws_wordv[j];
86   for (j = 1; i < argc; i++, j++)
87     argv[i] = (*pargv)[j];
88   argv[i] = NULL;
89 
90   ws.ws_wordc = 0;
91   mu_wordsplit_free (&ws);
92 
93   *pargc = argc;
94   *pargv = argv;
95 }
96 
97 static void
process_std_options(int argc,char ** argv,struct mu_parseopt * po)98 process_std_options (int argc, char **argv, struct mu_parseopt *po)
99 {
100   if (argc != 1)
101     return;
102   if (strcmp (argv[0], "--help") == 0)
103     {
104       mu_program_help (po, mu_strout);
105       exit (0);
106     }
107   if (strcmp (argv[0], "--version") == 0)
108     {
109       mu_version_hook (po, mu_strout);
110       exit (0);
111     }
112 }
113 
114 static void
process_folder_arg(int * pargc,char ** argv,struct mu_parseopt * po)115 process_folder_arg (int *pargc, char **argv, struct mu_parseopt *po)
116 {
117   int i, j;
118   int argc = *pargc;
119   struct mu_option *opt;
120 
121   /* Find folder option */
122   for (i = 0; ; i++)
123     {
124       if (!po->po_optv[i])
125 	return; /* Nothing to do */
126       if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i])
127 	  && strcmp (po->po_optv[i]->opt_long, "folder") == 0)
128 	break;
129     }
130   opt = po->po_optv[i];
131 
132   for (i = j = 0; i < argc; i++)
133     {
134       if (argv[i][0] == '+')
135 	{
136 	  opt->opt_set (po, opt, argv[i] + 1);
137 	}
138       else
139 	argv[j++] = argv[i];
140     }
141   argv[j] = NULL;
142   *pargc = j;
143 }
144 
145 void
mh_opt_set_folder(struct mu_parseopt * po,struct mu_option * opt,char const * arg)146 mh_opt_set_folder (struct mu_parseopt *po, struct mu_option *opt,
147 		   char const *arg)
148 {
149   mh_set_current_folder (arg);
150 }
151 
152 static struct mu_option folder_option[] = {
153   { "folder", 0, N_("FOLDER"), MU_OPTION_DEFAULT,
154     N_("set current folder"),
155     mu_c_string, NULL, mh_opt_set_folder },
156   MU_OPTION_END
157 };
158 
159 void
mh_version_hook(struct mu_parseopt * po,mu_stream_t stream)160 mh_version_hook (struct mu_parseopt *po, mu_stream_t stream)
161 {
162   mu_stream_printf (stream, "%s (%s %s)\n", mu_program_name,
163 		    PACKAGE_NAME, PACKAGE_VERSION);
164 #if MU_GIT_COMMIT_DISTANCE > 0
165   mu_stream_printf (stream, "version: %s %s-%d [%s]\n",
166 		    PACKAGE_NAME, PACKAGE_VERSION,
167 		    MU_GIT_COMMIT_DISTANCE,
168 		    MU_GIT_DESCRIBE_STRING);
169 #endif
170   /* TRANSLATORS: Translate "(C)" to the copyright symbol
171      (C-in-a-circle), if this symbol is available in the user's
172      locale.  Otherwise, do not translate "(C)"; leave it as-is.  */
173   mu_stream_printf (stream, mu_version_copyright, _("(C)"));
174   mu_stream_printf (stream, _("\
175 \n\
176 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you are free to change and redistribute it.\n\
177 There is NO WARRANTY, to the extent permitted by law.\n\
178 \n\
179 "));
180 }
181 
182 static int
has_folder_option(struct mu_option * opt)183 has_folder_option (struct mu_option *opt)
184 {
185   while (!MU_OPTION_IS_END (opt))
186     {
187       if (MU_OPTION_IS_VALID_LONG_OPTION (opt)
188 	  && strcmp (opt->opt_long, "folder") == 0)
189 	return 1;
190       ++opt;
191     }
192   return 0;
193 }
194 
195 static void
opt_init(struct mu_parseopt * po,struct mu_option ** optv,struct mh_optinit * optinit)196 opt_init (struct mu_parseopt *po,
197 	  struct mu_option **optv, struct mh_optinit *optinit)
198 {
199   if (!optinit)
200     return;
201   for (; optinit->opt; optinit++)
202     {
203       size_t i;
204       for (i = 0; optv[i]; i++)
205 	{
206 	  struct mu_option *opt;
207 	  for (opt = optv[i]; !MU_OPTION_IS_END (opt); opt++)
208 	    {
209 	      if (strcmp (opt->opt_long, optinit->opt) == 0)
210 		{
211 		  char const *val = mh_global_profile_get (optinit->var, NULL);
212 		  if (val)
213 		    {
214 		      (opt->opt_set ?
215 		       opt->opt_set : mu_option_set_value) (po, opt, val);
216 		    }
217 		  break;
218 		}
219 	    }
220 	}
221     }
222 }
223 
224 void
mh_getopt_ext(int * pargc,char *** pargv,struct mu_option * options,int mhflags,struct mh_optinit * optinit,char * argdoc,char * progdoc,char * extradoc)225 mh_getopt_ext (int *pargc, char ***pargv, struct mu_option *options,
226 	       int mhflags, struct mh_optinit *optinit,
227 	       char *argdoc, char *progdoc, char *extradoc)
228 {
229   int argc = *pargc;
230   char **argv = *pargv;
231   struct mu_parseopt po;
232   struct mu_option *optv[3];
233   struct getopt_data getopt_data;
234   char const *args[3];
235   int flags = MU_PARSEOPT_SINGLE_DASH | MU_PARSEOPT_IMMEDIATE;
236   int i;
237 
238   /* Native Language Support */
239   MU_APP_INIT_NLS ();
240 
241   po.po_negation = "no";
242   flags |= MU_PARSEOPT_NEGATION;
243 
244   if ((mhflags & MH_GETOPT_DEFAULT_FOLDER) || has_folder_option (options))
245     {
246       po.po_special_args = N_("[+FOLDER]");
247       flags |= MU_PARSEOPT_SPECIAL_ARGS;
248     }
249 
250   if (argdoc)
251     {
252       args[0] = argdoc;
253       args[1] = NULL;
254       po.po_prog_args = args;
255       flags |= MU_PARSEOPT_PROG_ARGS;
256     }
257   if (progdoc)
258     {
259       po.po_prog_doc = progdoc;
260       flags |= MU_PARSEOPT_PROG_DOC;
261     }
262 
263   getopt_data.extra_doc = extradoc;
264   if (extradoc)
265     {
266       po.po_help_hook = mh_extra_help_hook;
267       flags |= MU_PARSEOPT_HELP_HOOK;
268     }
269 
270   po.po_data = &getopt_data;
271   flags |= MU_PARSEOPT_DATA;
272 
273   po.po_exit_error = 1;
274   flags |= MU_PARSEOPT_EXIT_ERROR;
275 
276   po.po_package_name = PACKAGE_NAME;
277   flags |= MU_PARSEOPT_PACKAGE_NAME;
278 
279   po.po_package_url = PACKAGE_URL;
280   flags |= MU_PARSEOPT_PACKAGE_URL;
281 
282   po.po_bug_address = PACKAGE_BUGREPORT;
283   flags |= MU_PARSEOPT_BUG_ADDRESS;
284 
285   //po.po_extra_info = gnu_general_help_url;
286   //flags |= MU_PARSEOPT_EXTRA_INFO;
287 
288   po.po_version_hook = mh_version_hook;
289   flags |= MU_PARSEOPT_VERSION_HOOK;
290 
291   mu_set_program_name (argv[0]);
292   mh_init ();
293   augment_argv (&argc, &argv);
294 
295   i = 0;
296   if (mhflags & MH_GETOPT_DEFAULT_FOLDER)
297     optv[i++] = folder_option;
298   if (options)
299     optv[i++] = options;
300   optv[i] = NULL;
301   opt_init (&po, optv, optinit);
302 
303   if (mu_parseopt (&po, argc, argv, optv, flags))
304     exit (po.po_exit_error);
305 
306   argc -= po.po_arg_start;
307   argv += po.po_arg_start;
308 
309   process_std_options (argc, argv, &po);
310 
311   process_folder_arg (&argc, argv, &po);
312 
313   if (!argdoc && argc)
314     {
315       mu_diag_init ();
316       mu_stream_printf (mu_strerr, "\033s<%d>", MU_DIAG_ERROR);
317       mu_stream_printf (mu_strerr, "%s", _("unrecognized extra arguments:"));
318       for (i = 0; i < argc; i++)
319 	mu_stream_printf (mu_strerr, " %s", argv[i]);
320       mu_stream_write (mu_strerr, "\n", 1, NULL);
321       exit (1);
322     }
323 
324   *pargc = argc;
325   *pargv = argv;
326 
327   mh_init2 ();
328 }
329 
330 void
mh_getopt(int * pargc,char *** pargv,struct mu_option * options,int mhflags,char * argdoc,char * progdoc,char * extradoc)331 mh_getopt (int *pargc, char ***pargv, struct mu_option *options,
332 	   int mhflags, char *argdoc, char *progdoc, char *extradoc)
333 {
334   mh_getopt_ext (pargc, pargv, options, mhflags, NULL, argdoc, progdoc,
335 		 extradoc);
336 }
337 
338 void
mh_opt_notimpl(struct mu_parseopt * po,struct mu_option * opt,char const * arg)339 mh_opt_notimpl (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
340 {
341   mu_error (_("option is not yet implemented: %s"), opt->opt_long);
342   exit (1);
343 }
344 
345 void
mh_opt_notimpl_warning(struct mu_parseopt * po,struct mu_option * opt,char const * arg)346 mh_opt_notimpl_warning (struct mu_parseopt *po, struct mu_option *opt,
347 			char const *arg)
348 {
349   if (opt->opt_type == mu_c_bool)
350     {
351       int val;
352       if (mu_str_to_c (arg, opt->opt_type, &val, NULL) == 0 && !val)
353 	return;
354     }
355   mu_error (_("ignoring not implemented option %s"), opt->opt_long);
356 }
357 
358 void
mh_opt_clear_string(struct mu_parseopt * po,struct mu_option * opt,char const * arg)359 mh_opt_clear_string (struct mu_parseopt *po, struct mu_option *opt,
360 		     char const *arg)
361 {
362   char **sptr = opt->opt_ptr;
363   free (*sptr);
364   *sptr = NULL;
365 }
366 
367 void
mh_opt_find_file(struct mu_parseopt * po,struct mu_option * opt,char const * arg)368 mh_opt_find_file (struct mu_parseopt *po, struct mu_option *opt,
369 		  char const *arg)
370 {
371   mh_find_file (arg, opt->opt_ptr);
372 }
373 
374 void
mh_opt_read_formfile(struct mu_parseopt * po,struct mu_option * opt,char const * arg)375 mh_opt_read_formfile (struct mu_parseopt *po, struct mu_option *opt,
376 		      char const *arg)
377 {
378   mh_read_formfile (arg, opt->opt_ptr);
379 }
380 
381 void
mh_opt_parse_formfile(struct mu_parseopt * po,struct mu_option * opt,char const * arg)382 mh_opt_parse_formfile (struct mu_parseopt *po, struct mu_option *opt,
383 		       char const *arg)
384 {
385   mh_format_destroy ((mh_format_t*) opt->opt_ptr);
386   if (mh_format_file_parse (opt->opt_ptr, arg, MH_FMT_PARSE_DEFAULT))
387     exit (1);
388 }
389 
390 void
mh_opt_parse_format(struct mu_parseopt * po,struct mu_option * opt,char const * arg)391 mh_opt_parse_format (struct mu_parseopt *po, struct mu_option *opt,
392 		     char const *arg)
393 {
394   mh_format_destroy ((mh_format_t*) opt->opt_ptr);
395   if (mh_format_string_parse (opt->opt_ptr, arg, NULL, MH_FMT_PARSE_DEFAULT))
396     exit (1);
397 }
398