1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2007-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 #include <stdio.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <sys/stat.h>
24 #include <mailutils/mailutils.h>
25 
26 char *file;
27 mu_header_t header;
28 mu_iterator_t iterator;
29 mu_stream_t hstream;
30 
31 char *ps[] = { "> ", ". " };
32 int interactive;
33 
34 static void
prompt(int l)35 prompt(int l)
36 {
37   if (interactive)
38     {
39       printf ("%s", ps[l]);
40       fflush (stdout);
41     }
42 }
43 
44 static int
load_file(const char * name)45 load_file (const char *name)
46 {
47   struct stat st;
48   size_t nread;
49   char *buf;
50   FILE *fp;
51   int status;
52 
53   if (stat (name, &st))
54     {
55       mu_error ("cannot stat %s: %s", name, mu_strerror (errno));
56       return 1;
57     }
58 
59   buf = malloc (st.st_size + 2);
60   if (!buf)
61     {
62       mu_error ("not enough memory");
63       return 1;
64     }
65 
66   fp = fopen (name, "r");
67   if (!fp)
68     {
69       mu_error ("cannot open file %s: %s", name, mu_strerror (errno));
70       free (buf);
71       return 1;
72     }
73 
74   nread = fread (buf, 1, st.st_size, fp);
75   fclose (fp);
76   if (nread != st.st_size)
77     {
78       mu_error ("short read on file %s", name);
79       free (buf);
80       return 1;
81     }
82 
83   buf[st.st_size] = '\n';
84   buf[st.st_size+1] = 0;
85   status = mu_header_create (&header, buf, st.st_size + 1);
86   free (buf);
87   if (status)
88     {
89       mu_error ("cannot create header: %s", mu_strerror (status));
90       return 1;
91     }
92   return 0;
93 }
94 
95 unsigned line_num = 0;
96 
97 static int
check_args(char const * cmdname,int argc,int amin,int amax)98 check_args (char const *cmdname, int argc, int amin, int amax)
99 {
100   if (argc < amin)
101     {
102       mu_error ("%u: %s: too few arguments",
103 	       line_num, cmdname);
104       return 1;
105     }
106   if (amax > 0 && argc > amax)
107     {
108       mu_error ("%u: %s: too many arguments",
109 		line_num, cmdname);
110       return 1;
111     }
112   return 0;
113 }
114 
115 void
cmd_quit(int argc,char ** argv)116 cmd_quit (int argc, char **argv)
117 {
118   exit (0);
119 }
120 
121 void
cmd_load(int argc,char ** argv)122 cmd_load (int argc, char **argv)
123 {
124   if (check_args (argv[0], argc, 2, 2))
125     return;
126   mu_stream_destroy (&hstream);
127   mu_header_destroy (&header);
128   load_file (argv[1]);
129 }
130 
131 void
cmd_free(int argc,char ** argv)132 cmd_free (int argc, char **argv)
133 {
134   if (check_args (argv[0], argc, 1, 1))
135     return;
136   mu_iterator_destroy (&iterator);
137   mu_stream_destroy (&hstream);
138   mu_header_destroy (&header);
139 }
140 
141 void
cmd_print(int argc,char ** argv)142 cmd_print (int argc, char **argv)
143 {
144   char *fn;
145   int num = 1;
146   int status;
147   const char *str;
148 
149   if (check_args (argv[0], argc, 2, 3))
150     return;
151   fn = argv[1];
152   if (argc == 3)
153     num = atoi (argv[2]);
154 
155   status = mu_header_sget_value_n (header, fn, num, &str);
156   if (status == 0)
157     printf ("%s: %s\n", fn, str);
158   else
159     mu_error ("%u: %s", line_num, mu_strerror (status));
160 }
161 
162 void
cmd_dump(int argc,char ** argv)163 cmd_dump (int argc, char **argv)
164 {
165   mu_off_t off = 0;
166   size_t n;
167   mu_stream_t stream;
168   char buf[512];
169   int status;
170 
171   if (check_args (argv[0], argc, 1, 2))
172     return;
173 
174   if (argc == 2)
175     off = strtoul (argv[1], NULL, 0);
176 
177   status = mu_header_get_streamref (header, &stream);
178   if (status)
179     {
180       mu_error ("%u: cannot get stream: %s", line_num, mu_strerror (status));
181       return;
182     }
183 
184   status = mu_stream_seek (stream, off, SEEK_SET, NULL);
185   if (status)
186     {
187       mu_stream_destroy (&stream);
188       mu_error ("%u: cannot seek: %s", line_num, mu_strerror (status));
189       return;
190     }
191 
192   while (mu_stream_read (stream, buf, sizeof buf, &n) == 0
193 	 && n > 0)
194     {
195       fwrite (buf, 1, n, stdout);
196     }
197   mu_stream_destroy (&stream);
198 }
199 
200 void
cmd_remove(int argc,char ** argv)201 cmd_remove (int argc, char **argv)
202 {
203   char *fn;
204   int num = 1;
205   int status;
206 
207   if (check_args (argv[0], argc, 2, 3))
208     return;
209   fn = argv[1];
210   if (argc == 3)
211     num = atoi (argv[2]);
212   status = mu_header_remove (header, fn, num);
213   if (status)
214     mu_error ("%u: %s: %s", line_num, argv[0], mu_strerror (status));
215   mu_stream_destroy (&hstream);
216 }
217 
218 /* insert header value [ref [num] [before|after] [replace]] */
219 void
cmd_insert(int argc,char ** argv)220 cmd_insert (int argc, char **argv)
221 {
222   int status;
223   int flags = 0;
224   char *ref = NULL;
225   int num = 1;
226   int n;
227 
228   if (check_args (argv[0], argc, 3, 7))
229     return;
230 
231   if (argc >= 4)
232     {
233       ref = argv[3];
234       n = 4;
235       if (n < argc)
236 	{
237 	  char *p;
238 	  int tmp;
239 
240 	  tmp = strtoul(argv[4], &p, 0);
241 	  if (*p == 0)
242 	    {
243 	      num = tmp;
244 	      n++;
245 	    }
246 
247 	  for (; n < argc; n++)
248 	    {
249 	      if (strcmp(argv[n], "before") == 0)
250 		flags |= MU_HEADER_BEFORE;
251 	      else if (strcmp(argv[n], "after") == 0)
252 		;
253 	      else if (strcmp(argv[n], "replace") == 0)
254 		flags |= MU_HEADER_REPLACE;
255 	      else
256 		{
257 		  mu_error("%u: %s: unknown option", line_num, argv[4]);
258 		  return;
259 		}
260 	    }
261 	}
262     }
263   status = mu_header_insert (header, argv[1], argv[2],
264 			     ref, num, flags);
265   mu_stream_destroy (&hstream);
266   if (status)
267     mu_error ("%u: %s: %s", line_num, argv[0], mu_strerror (status));
268 }
269 
270 void
cmd_write(int argc,char ** argv)271 cmd_write (int argc, char **argv)
272 {
273   char buf[512];
274   mu_stream_t str;
275   int status;
276 
277   if (check_args (argv[0], argc, 1, 1))
278     return;
279 
280   status = mu_header_get_streamref (header, &str);
281   if (status)
282     {
283       mu_error ("%u: cannot get stream: %s", line_num, mu_strerror (status));
284       return;
285     }
286   printf ("[reading headers; end with an empty line]\n");
287   mu_stream_seek (str, 0, SEEK_SET, NULL);
288   while (prompt (1), fgets(buf, sizeof buf, stdin))
289     {
290       mu_stream_write (str, buf, strlen (buf), NULL);
291       if (buf[0] == '\n')
292 	break;
293     }
294   mu_stream_destroy (&str);
295   mu_stream_destroy (&hstream);
296 }
297 
298 void
cmd_overwrite(int argc,char ** argv)299 cmd_overwrite (int argc, char **argv)
300 {
301   char buf[512];
302   mu_stream_t str;
303   int status;
304   mu_off_t off;
305 
306   if (check_args (argv[0], argc, 2, 2))
307     return;
308 
309   off = strtoul (argv[1], NULL, 0);
310 
311   status = mu_header_get_streamref (header, &str);
312   if (status)
313     {
314       mu_error ("%u: cannot get stream: %s", line_num, mu_strerror (status));
315       return;
316     }
317   status = mu_stream_seek (str, off, SEEK_SET, NULL);
318   if (status)
319     {
320       mu_error ("seek error: %s", mu_strerror (status));
321       return;
322     }
323 
324   printf ("[reading headers; end with an empty line]\n");
325   while (prompt (1), fgets(buf, sizeof buf, stdin))
326     {
327       if (buf[0] == '\n')
328 	break;
329       mu_stream_write (str, buf, strlen (buf), NULL);
330     }
331   mu_stream_destroy (&str);
332   mu_stream_destroy (&hstream);
333 }
334 
335 void
cmd_append(int argc,char ** argv)336 cmd_append (int argc, char **argv)
337 {
338   char buf[512];
339   mu_stream_t str;
340   int status;
341 
342   if (check_args (argv[0], argc, 1, 1))
343     return;
344 
345   status = mu_header_get_streamref (header, &str);
346   if (status)
347     {
348       mu_error ("%u: cannot get stream: %s", line_num, mu_strerror (status));
349       return;
350     }
351   printf ("[reading headers; end with an empty line]\n");
352   mu_stream_seek (str, 0, SEEK_END, NULL);
353   while (prompt (1), fgets(buf, sizeof buf, stdin))
354     {
355       mu_stream_write (str, buf, strlen (buf), NULL);
356       if (buf[0] == '\n')
357 	break;
358     }
359   mu_stream_destroy (&str);
360   mu_stream_destroy (&hstream);
361 }
362 
363 void
cmd_iterate(int argc,char ** argv)364 cmd_iterate (int argc, char **argv)
365 {
366   if (check_args (argv[0], argc, 1, 2))
367     return;
368   if (argc == 1)
369     {
370       mu_iterator_t itr;
371       MU_ASSERT (mu_header_get_iterator (header, &itr));
372       for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
373 	   mu_iterator_next (itr))
374 	{
375 	  const char *hdr, *val;
376 	  MU_ASSERT (mu_iterator_current_kv (itr,
377 					     (const void**)&hdr,
378 					     (void**)&val));
379 	  printf ("%s: %s\n", hdr, val);
380 	}
381       mu_iterator_destroy (&itr);
382     }
383   else
384     {
385       const char *hdr, *val;
386 
387       if (!iterator)
388 	MU_ASSERT (mu_header_get_iterator (header, &iterator));
389 
390       if (strcmp (argv[1], "first") == 0 || strcmp (argv[1], "1") == 0)
391 	mu_iterator_first (iterator);
392       else if (strcmp (argv[1], "next") == 0 || strcmp (argv[1], "n") == 0)
393 	{
394 	  mu_iterator_next (iterator);
395 	  if (mu_iterator_is_done (iterator))
396 	    {
397 	      printf ("Past end of headers. Use `itr first'.\n");
398 	      return;
399 	    }
400 	}
401 
402       MU_ASSERT (mu_iterator_current_kv (iterator,
403 					 (const void **)&hdr,
404 					 (void**)&val));
405       printf ("%s: %s\n", hdr, val);
406     }
407 }
408 
409 void
cmd_readline(int argc,char ** argv)410 cmd_readline (int argc, char **argv)
411 {
412   char *buf;
413   size_t size = 128;
414   size_t nbytes;
415 
416   if (check_args (argv[0], argc, 1, 2))
417     return;
418   if (argc == 2)
419     size = atoi (argv[1]);
420   buf = malloc (size);
421   if (!buf)
422     abort ();
423   if (!hstream)
424     mu_header_get_streamref (header, &hstream);
425   mu_stream_readline (hstream, buf, size, &nbytes);
426   printf ("\"%*.*s\"", (int) nbytes, (int) nbytes, buf);
427   free (buf);
428 }
429 
430 
431 struct cmdtab
432 {
433   char *name;
434   void (*fun) (int argc, char **argv);
435   char *args;
436   char *help;
437 };
438 
439 static void cmd_help (int argc, char **argv);
440 
441 static struct cmdtab cmdtab[] = {
442   { "quit", cmd_quit, NULL, "quit the program" },
443   { "load", cmd_load, "FILE", "read headers from the specified FILE" },
444   { "free", cmd_free, NULL, "discard all headers" },
445   { "print", cmd_print, "NAME [N]",
446     "find and print the Nth (by default, 1st) instance of header named NAME" },
447   { "dump", cmd_dump, "[OFF]", "dump all headers on screen" },
448   { "itr", cmd_iterate, "[first|1|next|n]", "iterate over headers" },
449   { "readline", cmd_readline, "[SIZE]", "read line" },
450   { "remove", cmd_remove, "NAME [N]",
451     "remove the Nth (by default, 1st) instance of header named NAME" },
452   { "insert", cmd_insert, "NAME VALUE [REF [NUM] [before|after] [replace]]",
453     "insert new header" },
454   { "write", cmd_write, NULL, "accept headers from raw stream" },
455   { "overwrite", cmd_overwrite, "OFF", "overwrite raw data from offset OFF" },
456   { "append", cmd_append, NULL, "append raw data" },
457   { "help", cmd_help, "[COMMAND]", "print short usage message" },
458   { NULL }
459 };
460 
461 static struct cmdtab *
find_cmd(const char * name)462 find_cmd (const char *name)
463 {
464   struct cmdtab *p;
465   for (p = cmdtab; p->name; p++)
466     if (strcmp (p->name, name) == 0)
467       return p;
468   return NULL;
469 }
470 
471 static void
format_help_str(int col,char * p)472 format_help_str (int col, char *p)
473 {
474   if (col > 31)
475     col = 80;
476   while (*p)
477     {
478       int len;
479       char *q;
480 
481       if (*p == ' ' || *p == '\t')
482 	{
483 	  p++;
484 	  continue;
485 	}
486 
487       q = strchr (p, ' ');
488       if (!q)
489 	len = strlen (p);
490       else
491 	len = q - p;
492 
493       if (col + len > 80)
494 	{
495 	  fputc ('\n', stdout);
496 	  for (col = 0; col < 30; col++)
497 	    fputc (' ', stdout);
498 	}
499       for (; len > 0; len--, p++, col++)
500 	fputc (*p, stdout);
501 
502       if (q)
503 	{
504 	  if (col < 80)
505 	    {
506 	      fputc (' ', stdout);
507 	      col++;
508 	    }
509 	  p++;
510 	}
511     }
512   fputc ('\n', stdout);
513 }
514 
515 
516 
517 void
cmd_help(int argc,char ** argv)518 cmd_help (int argc, char **argv)
519 {
520   struct cmdtab *p;
521 
522   if (check_args (argv[0], argc, 1, 2))
523     return;
524 
525   for (p = cmdtab; p->name; p++)
526     {
527       int col;
528 
529       col = printf ("%s", p->name);
530       for (; col < 10; col++)
531 	fputc (' ', stdout);
532       if (p->args)
533 	col += printf ("%s", p->args);
534       for (; col < 30; col++)
535 	fputc (' ', stdout);
536       format_help_str (col, p->help);
537     }
538 }
539 
540 int
docmd(int argc,char ** argv)541 docmd (int argc, char **argv)
542 {
543   struct cmdtab *cmd = find_cmd (argv[0]);
544   if (!cmd)
545     {
546       mu_error ("%u: unknown command %s", line_num, argv[0]);
547       return 1;
548     }
549   else
550     cmd->fun (argc, argv);
551   return 0;
552 }
553 
554 int
main(int argc,char ** argv)555 main (int argc, char **argv)
556 {
557   int c;
558   char buf[512];
559   char **prevv = NULL;
560   size_t prevc = 0;
561 
562   interactive = isatty (0);
563   while ((c = getopt (argc, argv, "f:h")) != EOF)
564     {
565       switch (c)
566 	{
567 	case 'f':
568 	  file = optarg;
569 	  break;
570 
571 	case 'h':
572 	  printf ("usage: header [-f file]\n");
573 	  exit (0);
574 
575 	default:
576 	  exit (1);
577 	}
578     }
579 
580   if (file)
581     {
582       if (load_file (file))
583 	exit (1);
584     }
585   else
586     {
587       int status = mu_header_create (&header, NULL, 0);
588       if (status)
589 	{
590 	  mu_error ("cannot create header: %s", mu_strerror (status));
591 	  exit (1);
592 	}
593     }
594 
595   while (prompt (0), fgets (buf, sizeof buf, stdin))
596     {
597       struct mu_wordsplit ws;
598 
599       line_num++;
600       ws.ws_comment = "#";
601       if (mu_wordsplit (buf, &ws, MU_WRDSF_DEFFLAGS | MU_WRDSF_COMMENT))
602 	{
603 	  mu_error ("cannot split line `%s': %s", buf,
604 		    mu_wordsplit_strerror (&ws));
605 	  continue;
606 	}
607 
608       if (ws.ws_wordc == 0)
609 	{
610 	  if (prevc)
611 	    docmd (prevc, prevv);
612 	}
613       else
614 	{
615 	  docmd (ws.ws_wordc, ws.ws_wordv);
616 	  mu_argcv_free (prevc, prevv);
617 	  mu_wordsplit_get_words (&ws, &prevc, &prevv);
618 	}
619       mu_wordsplit_free (&ws);
620     }
621   mu_argcv_free (prevc, prevv);
622   exit (0);
623 }
624 
625