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