]> diplodocus.org Git - nmh/blob - uip/mhshowsbr.c
3ed6f8b995d2ec9190707df89aa2a16fc0548cb6
[nmh] / uip / mhshowsbr.c
1 /* mhshowsbr.c -- routines to display the contents of MIME messages
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include "sbr/error.h"
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <h/md5.h>
13 #include <h/mts.h>
14 #include <h/tws.h>
15 #include <h/mime.h>
16 #include <h/mhparse.h>
17 #include <h/fmt_scan.h>
18 #include "h/done.h"
19 #include <h/utils.h>
20 #include "mhmisc.h"
21 #include "mhshowsbr.h"
22 #include "sbr/m_mktemp.h"
23 #ifdef HAVE_ICONV
24 # include <iconv.h>
25 #endif /* ! HAVE_ICONV */
26
27 extern int debugsw;
28
29 int nolist = 0;
30
31 char *progsw = NULL;
32
33 /* flags for moreproc/header display */
34 int nomore = 0;
35 char *formsw = NULL;
36
37 /* for output markers and headers */
38 char *folder = NULL;
39 char *markerform;
40 char *headerform;
41 int headersw = -1;
42
43
44 /*
45 * static prototypes
46 */
47 static void show_single_message (CT, char *, int, int, int, struct format *);
48 static void DisplayMsgHeader (CT, char *, int);
49 static int show_switch (CT, int, int, int, int, struct format *);
50 static int show_content (CT, int, int, int, struct format *);
51 static int show_content_aux2 (CT, int, char *, char *, int, int, int, struct format *);
52 static int show_text (CT, int, int, struct format *);
53 static int show_multi (CT, int, int, int, int, struct format *);
54 static int show_multi_internal (CT, int, int, int, int, struct format *);
55 static int show_multi_aux (CT, int, char *, struct format *);
56 static int show_message_rfc822 (CT, int, struct format *);
57 static int show_partial (CT, int);
58 static int show_external (CT, int, int, int, int, struct format *);
59 static int parse_display_string (CT, char *, int *, int *, char *, char *,
60 size_t, int multipart);
61 static int convert_content_charset (CT, char **);
62 static struct format *compile_header(char *);
63 static struct format *compile_marker(char *);
64 static void output_header (CT, struct format *);
65 static void output_marker (CT, struct format *, int);
66 static void free_markercomps (void);
67 static int pidcheck(int);
68
69 /*
70 * Components (and list of parameters/components) we care about for the
71 * content marker display.
72 */
73
74 static struct comp *folder_comp = NULL;
75 static struct comp *part_comp = NULL;
76 static struct comp *ctype_comp = NULL;
77 static struct comp *description_comp = NULL;
78 static struct comp *dispo_comp = NULL;
79
80 struct param_comp_list {
81 char *param;
82 struct comp *comp;
83 struct param_comp_list *next;
84 };
85
86 static struct param_comp_list *ctype_pc_list = NULL;
87 static struct param_comp_list *dispo_pc_list = NULL;
88
89
90 /*
91 * Top level entry point to show/display a group of messages
92 */
93
94 void
95 show_all_messages(CT *cts, int concat, int textonly, int inlineonly)
96 {
97 CT ct, *ctp;
98 struct format *hfmt, *mfmt;
99
100 /*
101 * If form is not specified, then get default form
102 * for showing headers of MIME messages.
103 */
104 if (!formsw)
105 formsw = mh_xstrdup(etcpath("mhl.headers"));
106
107 /*
108 * Compile the content marker and header format lines
109 */
110 mfmt = compile_marker(markerform);
111 hfmt = compile_header(headerform);
112
113 /*
114 * If form is "mhl.null", suppress display of header.
115 */
116 if (!strcmp (formsw, "mhl.null"))
117 formsw = NULL;
118
119 for (ctp = cts; *ctp; ctp++) {
120 ct = *ctp;
121
122 /* if top-level type is ok, then display message */
123 if (type_ok (ct, 1)) {
124 if (headersw) output_header(ct, hfmt);
125
126 show_single_message (ct, formsw, concat, textonly, inlineonly,
127 mfmt);
128 }
129 }
130
131 free_markercomps();
132 fmt_free(hfmt, 1);
133 fmt_free(mfmt, 1);
134 }
135
136
137 /*
138 * Entry point to show/display a single message
139 */
140
141 static void
142 show_single_message (CT ct, char *form, int concatsw, int textonly,
143 int inlineonly, struct format *fmt)
144 {
145 sigset_t set, oset;
146
147 int status = OK;
148
149 /* Allow user executable bit so that temporary directories created by
150 * the viewer (e.g., lynx) are going to be accessible */
151 umask (ct->c_umask & ~(0100));
152
153 /*
154 * If you have a format file, then display
155 * the message headers.
156 */
157 if (form)
158 DisplayMsgHeader(ct, form, concatsw);
159
160 /* Show the body of the message */
161 show_switch (ct, 0, concatsw, textonly, inlineonly, fmt);
162
163 if (ct->c_fp) {
164 fclose (ct->c_fp);
165 ct->c_fp = NULL;
166 }
167 if (ct->c_ceclosefnx)
168 (*ct->c_ceclosefnx) (ct);
169
170 /* block a few signals */
171 sigemptyset (&set);
172 sigaddset (&set, SIGHUP);
173 sigaddset (&set, SIGINT);
174 sigaddset (&set, SIGQUIT);
175 sigaddset (&set, SIGTERM);
176 sigprocmask (SIG_BLOCK, &set, &oset);
177
178 while (!concatsw && wait (&status) != NOTOK) {
179 pidcheck (status);
180 continue;
181 }
182
183 /* reset the signal mask */
184 sigprocmask (SIG_SETMASK, &oset, &set);
185
186 flush_errors ();
187 }
188
189
190 /*
191 * Use the mhlproc to show the header fields
192 */
193
194 static void
195 DisplayMsgHeader (CT ct, char *form, int concatsw)
196 {
197 pid_t child_id;
198 int vecp;
199 char **vec;
200 char *file;
201
202 vec = argsplit(mhlproc, &file, &vecp);
203 vec[vecp++] = mh_xstrdup("-form");
204 vec[vecp++] = mh_xstrdup(form);
205 vec[vecp++] = mh_xstrdup("-nobody");
206 vec[vecp++] = getcpy(ct->c_file);
207
208 /*
209 * If we've specified -(no)moreproc,
210 * then just pass that along.
211 */
212 if (nomore || concatsw) {
213 vec[vecp++] = mh_xstrdup("-nomoreproc");
214 } else if (progsw) {
215 vec[vecp++] = mh_xstrdup("-moreproc");
216 vec[vecp++] = mh_xstrdup(progsw);
217 }
218 vec[vecp] = NULL;
219
220 fflush (stdout);
221
222 child_id = fork();
223 switch (child_id) {
224 case NOTOK:
225 adios ("fork", "unable to");
226 /* NOTREACHED */
227
228 case OK:
229 execvp (file, vec);
230 fprintf (stderr, "unable to exec ");
231 perror (mhlproc);
232 _exit(1);
233 /* NOTREACHED */
234
235 default:
236 pidcheck(pidwait(child_id, NOTOK));
237 break;
238 }
239
240 arglist_free(file, vec);
241 }
242
243
244 /*
245 * Switching routine. Call the correct routine
246 * based on content type.
247 */
248
249 static int
250 show_switch (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
251 struct format *fmt)
252 {
253 switch (ct->c_type) {
254 case CT_MULTIPART:
255 return show_multi (ct, alternate, concatsw, textonly,
256 inlineonly, fmt);
257
258 case CT_MESSAGE:
259 switch (ct->c_subtype) {
260 case MESSAGE_PARTIAL:
261 return show_partial (ct, alternate);
262
263 case MESSAGE_EXTERNAL:
264 return show_external (ct, alternate, concatsw, textonly,
265 inlineonly, fmt);
266
267 case MESSAGE_RFC822:
268 return show_message_rfc822 (ct, alternate, fmt);
269
270 /*
271 * Treat unknown message types as equivalent to
272 * application/octet-stream for now
273 */
274 default:
275 return show_content (ct, alternate, textonly,
276 inlineonly, fmt);
277 }
278
279 case CT_TEXT:
280 return show_text (ct, alternate, concatsw, fmt);
281
282 case CT_AUDIO:
283 case CT_IMAGE:
284 case CT_VIDEO:
285 case CT_APPLICATION:
286 default:
287 return show_content (ct, alternate, textonly, inlineonly, fmt);
288 }
289
290 return 0; /* NOT REACHED */
291 }
292
293
294 /*
295 * Generic method for displaying content
296 */
297
298 static int
299 show_content (CT ct, int alternate, int textonly, int inlineonly,
300 struct format *fmt)
301 {
302 char *cp;
303 CI ci = &ct->c_ctinfo;
304
305 /*
306 * If we're here, we are not a text type. So we don't need to check
307 * the content-type.
308 */
309
310 if (textonly || (inlineonly && !is_inline(ct))) {
311 output_marker(ct, fmt, 1);
312 return OK;
313 }
314
315 /* Check for invo_name-show-type[/subtype] */
316 if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
317 return show_content_aux (ct, alternate, cp, NULL, fmt);
318
319 if ((cp = ct->c_showproc))
320 return show_content_aux (ct, alternate, cp, NULL, fmt);
321
322 /* complain if we are not a part of a multipart/alternative */
323 if (!alternate)
324 content_error (NULL, ct, "don't know how to display content");
325
326 return NOTOK;
327 }
328
329
330 /*
331 * Parse the display string for displaying generic content
332 */
333
334 int
335 show_content_aux (CT ct, int alternate, char *cp, char *cracked, struct format *fmt)
336 {
337 int fd;
338 int xstdin = 0, xlist = 0;
339 char *file = NULL, buffer[NMH_BUFSIZ];
340
341 if (!ct->c_ceopenfnx) {
342 if (!alternate)
343 content_error (NULL, ct, "don't know how to decode content");
344
345 return NOTOK;
346 }
347
348 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
349 return NOTOK;
350 if (ct->c_showproc && !strcmp (ct->c_showproc, "true"))
351 return OK;
352
353 if (! strcmp(invo_name, "mhshow") &&
354 ct->c_type == CT_TEXT && ct->c_subtype == TEXT_PLAIN) {
355 /* This has to be done after calling c_ceopenfnx, so
356 unfortunately the type checks are necessary without
357 some code rearrangement. And to make this really ugly,
358 only do it in mhshow, not mhfixmsg, mhn, or mhstore. */
359 if (convert_content_charset (ct, &file) == OK) {
360 (*ct->c_ceclosefnx) (ct);
361 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
362 return NOTOK;
363 } else {
364 char *charset = content_charset (ct);
365 inform("unable to convert character set%s%s from %s, continuing...",
366 ct->c_partno ? " of part " : "",
367 FENDNULL(ct->c_partno),
368 charset);
369 free (charset);
370 }
371 }
372
373 if (cracked) {
374 strncpy (buffer, cp, sizeof(buffer));
375 goto got_command;
376 }
377
378 if (parse_display_string (ct, cp, &xstdin, &xlist, file, buffer,
379 sizeof(buffer) - 1, 0)) {
380 inform("Buffer overflow constructing show command, continuing...");
381 return NOTOK;
382 }
383
384 got_command:
385 return show_content_aux2 (ct, alternate, cracked, buffer,
386 fd, xlist, xstdin, fmt);
387 }
388
389
390 /*
391 * Routine to actually display the content
392 */
393
394 static int
395 show_content_aux2 (CT ct, int alternate, char *cracked, char *buffer,
396 int fd, int xlist, int xstdin, struct format *fmt)
397 {
398 pid_t child_id;
399 int vecp;
400 char **vec, *file;
401
402 if (debugsw || cracked) {
403 fflush (stdout);
404
405 fprintf (stderr, "%s msg %s", cracked ? "storing" : "show",
406 ct->c_file);
407 if (ct->c_partno)
408 fprintf (stderr, " part %s", ct->c_partno);
409 if (cracked)
410 fprintf (stderr, " using command (cd %s; %s)\n", cracked, buffer);
411 else
412 fprintf (stderr, " using command %s\n", buffer);
413 }
414
415 if (xlist && fmt) {
416 output_marker(ct, fmt, 0);
417 }
418
419 /*
420 * If the command is a zero-length string, just write the output on
421 * stdout.
422 */
423
424 if (buffer[0] == '\0') {
425 char readbuf[BUFSIZ];
426 ssize_t cc;
427 char lastchar = '\n';
428
429 if (fd == NOTOK) {
430 inform("Cannot use NULL command to display content-type "
431 "%s/%s", ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype);
432 return NOTOK;
433 }
434
435 while ((cc = read(fd, readbuf, sizeof(readbuf))) > 0) {
436 if ((ssize_t) fwrite(readbuf, 1, cc, stdout) < cc) {
437 advise ("putline", "fwrite");
438 }
439 lastchar = readbuf[cc - 1];
440 }
441
442 if (cc < 0) {
443 advise("read", "while reading text content");
444 return NOTOK;
445 }
446
447 /*
448 * The MIME standards allow content to not have a trailing newline.
449 * But because we are (presumably) sending this to stdout, include
450 * a newline for text content if the final character was not a
451 * newline. Only do this for mhshow.
452 */
453
454 if (strcmp(invo_name, "mhshow") == 0 && ct->c_type == CT_TEXT &&
455 ct->c_subtype == TEXT_PLAIN && lastchar != '\n') {
456 putchar('\n');
457 }
458
459 fflush(stdout);
460
461 return OK;
462 }
463
464 vec = argsplit(buffer, &file, &vecp);
465 vec[vecp++] = NULL;
466
467 fflush (stdout);
468
469 child_id = fork();
470 switch (child_id) {
471 case NOTOK:
472 advise ("fork", "unable to");
473 (*ct->c_ceclosefnx) (ct);
474 return NOTOK;
475
476 case OK:
477 if (cracked) {
478 if (chdir (cracked) < 0) {
479 advise (cracked, "chdir");
480 }
481 }
482 if (!xstdin)
483 dup2 (fd, 0);
484 close (fd);
485 execvp (file, vec);
486 fprintf (stderr, "unable to exec ");
487 perror (buffer);
488 _exit(1);
489 /* NOTREACHED */
490
491 default: {
492 int status;
493 char *display_prog = vecp > 2 && vec[2][0] != '\0'
494 /* Copy the real display program name. This relies on the
495 specific construction of vec[] by argsplit(). */
496 ? vec[2]
497 : NULL;
498
499 pidcheck ((status = pidXwait (child_id, display_prog)));
500
501 arglist_free(file, vec);
502 if (fd != NOTOK)
503 (*ct->c_ceclosefnx) (ct);
504 return alternate ? OK : status;
505 }
506 }
507 }
508
509
510 /*
511 * show content of type "text"
512 */
513
514 static int
515 show_text (CT ct, int alternate, int concatsw, struct format *fmt)
516 {
517 char *cp, buffer[BUFSIZ];
518 CI ci = &ct->c_ctinfo;
519
520 /* Check for invo_name-show-type[/subtype] */
521 if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
522 return show_content_aux (ct, alternate, cp, NULL, fmt);
523
524 /*
525 * Use default method if content is text/plain, or if
526 * if it is not a text part of a multipart/alternative
527 */
528 if (!alternate || ct->c_subtype == TEXT_PLAIN) {
529 if (concatsw) {
530 if (ct->c_termproc)
531 snprintf(buffer, sizeof(buffer), "%%lcat");
532 else
533 snprintf(buffer, sizeof(buffer), "%%l");
534 } else
535 snprintf (buffer, sizeof(buffer), "%%l%s %%F", progsw ? progsw :
536 moreproc && *moreproc ? moreproc : DEFAULT_PAGER);
537 cp = (ct->c_showproc = mh_xstrdup(buffer));
538 return show_content_aux (ct, alternate, cp, NULL, fmt);
539 }
540
541 return NOTOK;
542 }
543
544
545 /*
546 * show message body of type "multipart"
547 */
548
549 static int
550 show_multi (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
551 struct format *fmt)
552 {
553 char *cp;
554 CI ci = &ct->c_ctinfo;
555
556 /* Check for invo_name-show-type[/subtype] */
557 if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
558 return show_multi_aux (ct, alternate, cp, fmt);
559
560 if ((cp = ct->c_showproc)) {
561 return show_multi_aux (ct, alternate, cp, fmt);
562 }
563
564 /*
565 * Use default method to display this multipart content. Even
566 * unknown types are displayable, since they're treated as mixed
567 * per RFC 2046.
568 */
569 return show_multi_internal (ct, alternate, concatsw, textonly,
570 inlineonly, fmt);
571 }
572
573
574 /*
575 * show message body of subtypes of multipart that
576 * we understand directly (mixed, alternate, etc...)
577 */
578
579 static int
580 show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
581 int inlineonly, struct format *fmt)
582 {
583 int alternating, nowalternate, result;
584 struct multipart *m = (struct multipart *) ct->c_ctparams;
585 struct part *part;
586 bool request_matched;
587 bool display_success;
588 bool mult_alt_done;
589 int ret;
590 CT p;
591
592 alternating = 0;
593 nowalternate = alternate;
594
595 if (ct->c_subtype == MULTI_ALTERNATE) {
596 nowalternate = 1;
597 alternating = 1;
598 }
599
600 /*
601 * alternate -> we are a part inside a multipart/alternative
602 * alternating -> we are a multipart/alternative
603 */
604
605 result = NOTOK;
606 request_matched = false;
607 display_success = false;
608 mult_alt_done = false;
609
610 for (part = m->mp_parts; part; part = part->mp_next) {
611 p = part->mp_part;
612
613 /* while looking for the right displayable alternative, we
614 * use a looser search criterion than we do after finding it.
615 * specifically, while still looking, part_ok() will match
616 * "parent" parts (e.g. "-part 2" where 2 is a high-level
617 * multipart). after finding it, we use part_exact() to only
618 * choose a part that was requested explicitly.
619 */
620 if ((part_exact(p) && type_ok(p, 1)) ||
621 (!mult_alt_done && part_ok (p) && type_ok (p, 1))) {
622
623 int inneresult;
624
625 inneresult = show_switch (p, nowalternate, concatsw, textonly,
626 inlineonly, fmt);
627 switch (inneresult) {
628 case NOTOK: /* hard display error */
629 request_matched = true;
630 if (alternate && !alternating) {
631 result = NOTOK;
632 goto out;
633 }
634 continue;
635
636 case DONE: /* found no match on content type */
637 continue;
638
639 case OK: /* display successful */
640 request_matched = true;
641 display_success = true;
642 result = OK;
643
644 /* if we got success on a sub-part of
645 * multipart/alternative, we're done, unless
646 * there's a chance an explicit part should be
647 * matched later in the alternatives. */
648 if (alternating) {
649 mult_alt_done = true;
650 } else if (alternate) {
651 alternate = nowalternate = 0;
652 }
653 continue;
654 }
655 break;
656 }
657 }
658
659 /* we're supposed to be displaying at least something from a
660 * multipart/alternative. if we've had parts to consider, and
661 * we've had no success, then we should complain. we shouldn't
662 * complain if none of the parts matched any -part or -type option.
663 */
664 if (alternating && request_matched && !display_success) {
665 /* if we're ourselves an alternate. don't complain yet. */
666 if (!alternate)
667 content_error (NULL, ct, "don't know how to display any of the contents");
668 result = NOTOK;
669 }
670
671 out:
672 /* if no parts matched what was requested, there can't have been
673 * any display errors. we report DONE rather than OK. */
674 ret = request_matched ? result : DONE;
675 return ret;
676 }
677
678
679 /*
680 * Parse display string for multipart content
681 * and use external program to display it.
682 */
683
684 static int
685 show_multi_aux (CT ct, int alternate, char *cp, struct format *fmt)
686 {
687 /* xstdin is only used in the call to parse_display_string():
688 its value is ignored in the function. */
689 int xstdin = 0, xlist = 0;
690 char *file = NULL, buffer[BUFSIZ];
691 struct multipart *m = (struct multipart *) ct->c_ctparams;
692 struct part *part;
693 CT p;
694
695 for (part = m->mp_parts; part; part = part->mp_next) {
696 p = part->mp_part;
697
698 if (!p->c_ceopenfnx) {
699 if (!alternate)
700 content_error (NULL, p, "don't know how to decode content");
701 return NOTOK;
702 }
703
704 if (p->c_storage == NULL) {
705 if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
706 return NOTOK;
707
708 p->c_storage = mh_xstrdup(FENDNULL(file));
709
710 if (p->c_showproc && !strcmp (p->c_showproc, "true"))
711 return OK;
712 (*p->c_ceclosefnx) (p);
713 }
714 }
715
716 if (parse_display_string (ct, cp, &xstdin, &xlist, file,
717 buffer, sizeof(buffer) - 1, 1)) {
718 inform("Buffer overflow constructing show command, continuing...");
719 return NOTOK;
720 }
721
722 return show_content_aux2 (ct, alternate, NULL, buffer, NOTOK, xlist, 0, fmt);
723 }
724
725
726 /*
727 * show content of type "message/rfc822"
728 */
729
730 static int
731 show_message_rfc822 (CT ct, int alternate, struct format *fmt)
732 {
733 char *cp;
734 CI ci = &ct->c_ctinfo;
735
736 /* Check for invo_name-show-type[/subtype] */
737 if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
738 return show_content_aux (ct, alternate, cp, NULL, fmt);
739
740 if ((cp = ct->c_showproc))
741 return show_content_aux (ct, alternate, cp, NULL, fmt);
742
743 /* default method for message/rfc822 */
744 if (ct->c_subtype == MESSAGE_RFC822) {
745 cp = (ct->c_showproc = mh_xstrdup("%pshow -file %F"));
746 return show_content_aux (ct, alternate, cp, NULL, fmt);
747 }
748
749 /* complain if we are not a part of a multipart/alternative */
750 if (!alternate)
751 content_error (NULL, ct, "don't know how to display content");
752
753 return NOTOK;
754 }
755
756
757 /*
758 * Show content of type "message/partial".
759 */
760
761 static int
762 show_partial (CT ct, int alternate)
763 {
764 NMH_UNUSED (alternate);
765
766 content_error (NULL, ct,
767 "in order to display this message, you must reassemble it");
768 return NOTOK;
769 }
770
771
772 /*
773 * Show content of type "message/external".
774 *
775 * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
776 */
777
778 static int
779 show_external (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
780 struct format *fmt)
781 {
782 struct exbody *e = (struct exbody *) ct->c_ctparams;
783 CT p = e->eb_content;
784
785 if (!type_ok (p, 0))
786 return OK;
787
788 return show_switch (p, alternate, concatsw, textonly, inlineonly, fmt);
789 }
790
791
792 static int
793 parse_display_string (CT ct, char *cp, int *xstdin, int *xlist,
794 char *file, char *buffer, size_t buflen,
795 int multipart)
796 {
797 int len;
798 bool quoted = false;
799 char *bp = buffer, *pp;
800 CI ci = &ct->c_ctinfo;
801
802 bp[0] = bp[buflen] = '\0';
803
804 for ( ; *cp && buflen > 0; cp++) {
805 if (*cp == '%') {
806 pp = bp;
807
808 switch (*++cp) {
809 case 'a':
810 /* insert parameters from Content-Type field */
811 {
812 PM pm;
813 char *s = "";
814
815 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
816 snprintf (bp, buflen, "%s%s=\"%s\"", s, pm->pm_name,
817 get_param_value(pm, '?'));
818 len = strlen (bp);
819 bp += len;
820 buflen -= len;
821 s = " ";
822 }
823 }
824 break;
825
826 case 'd':
827 /* insert content description */
828 if (ct->c_descr) {
829 char *s;
830
831 s = trimcpy (ct->c_descr);
832 strncpy (bp, s, buflen);
833 free (s);
834 }
835 break;
836
837 case 'e':
838 /* no longer implemented */
839 break;
840
841 case 'F':
842 /* %f, and stdin is terminal not content */
843 *xstdin = 1;
844 /* FALLTHRU */
845
846 case 'f':
847 if (multipart) {
848 /* insert filename(s) containing content */
849 struct multipart *m = (struct multipart *) ct->c_ctparams;
850 struct part *part;
851 char *s = "";
852 CT p;
853
854 for (part = m->mp_parts; part; part = part->mp_next) {
855 p = part->mp_part;
856
857 snprintf (bp, buflen, "%s%s", s, p->c_storage);
858 len = strlen (bp);
859 bp += len;
860 buflen -= len;
861 s = " ";
862 }
863 } else {
864 /* insert filename containing content */
865 snprintf (bp, buflen, "%s", file);
866
867 /*
868 * Old comments below are left here for posterity.
869 * This was/is tricky.
870 */
871 /* since we've quoted the file argument, set things up
872 * to look past it, to avoid problems with the quoting
873 * logic below. (I know, I should figure out what's
874 * broken with the quoting logic, but..)
875 */
876 /*
877 * Here's the email that submitted the patch with
878 * the comment above:
879 * https://www.mail-archive.com/nmh-workers@mhost.com/
880 * msg00288.html
881 * I can't tell from that exactly what was broken,
882 * beyond misquoting of the filename. The profile
883 * had appearances of %F both with and without quotes.
884 * The unquoted ones should have been quoted by the
885 * code below.
886 * The fix was to always quote the filename. But
887 * that broke '%F' because it expanded to ''filename''.
888 */
889 /*
890 * Old comments above are left here for posterity.
891 * The quoting below should work properly now.
892 */
893 }
894 break;
895
896 case 'p':
897 /* No longer supported */
898 /* FALLTHRU */
899
900 case 'l':
901 /* display listing prior to displaying content */
902 *xlist = !nolist;
903 break;
904
905 case 's':
906 /* insert subtype of content */
907 strncpy (bp, ci->ci_subtype, buflen);
908 break;
909
910 case '%':
911 /* insert character % */
912 goto raw;
913
914 case '{' : {
915 const char *closing_brace = strchr(cp, '}');
916
917 if (closing_brace) {
918 const size_t param_len = closing_brace - cp - 1;
919 char *param = mh_xmalloc(param_len + 1);
920 char *value;
921
922 (void) strncpy(param, cp + 1, param_len);
923 param[param_len] = '\0';
924 value = get_param(ci->ci_first_pm, param, '?', 0);
925 free(param);
926
927 cp += param_len + 1; /* Skip both braces, too. */
928
929 if (value) {
930 /* %{param} is set in the Content-Type header.
931 After the break below, quote it if necessary. */
932 (void) strncpy(bp, value, buflen);
933 free(value);
934 } else {
935 /* %{param} not found, so skip it completely. cp
936 was advanced above. */
937 continue;
938 }
939 } else {
940 /* This will get confused if there are multiple %{}'s,
941 but its real purpose is to avoid doing bad things
942 above if a closing brace wasn't found. */
943 inform("no closing brace for display string escape %s, continuing...",
944 cp);
945 }
946 break;
947 }
948
949 default:
950 *bp++ = *--cp;
951 *bp = '\0';
952 buflen--;
953 continue;
954 }
955 len = strlen (bp);
956 bp += len;
957 buflen -= len;
958 *bp = '\0';
959
960 /* Did we actually insert something? */
961 if (bp != pp) {
962 /* Insert single quote if not inside quotes already */
963 if (!quoted && buflen) {
964 len = strlen (pp);
965 memmove (pp + 1, pp, len+1);
966 *pp++ = '\'';
967 buflen--;
968 bp++;
969 quoted = true;
970 }
971 /* Escape existing quotes */
972 while ((pp = strchr (pp, '\'')) && buflen > 3) {
973 len = strlen (pp++);
974 if (quoted) {
975 /* Quoted. Let this quote close that quoting.
976 Insert an escaped quote to replace it and
977 another quote to reopen quoting, which will be
978 closed below. */
979 memmove (pp + 2, pp, len);
980 *pp++ = '\\';
981 *pp++ = '\'';
982 buflen -= 2;
983 bp += 2;
984 quoted = false;
985 } else {
986 /* Not quoted. This should not be reached with
987 the current code, but handle the condition
988 in case the code changes. Just escape the
989 quote. */
990 memmove (pp, pp-1, len+1);
991 *(pp++-1) = '\\';
992 buflen--;
993 bp++;
994 }
995 }
996 /* If pp is still set, that means we ran out of space. */
997 if (pp)
998 buflen = 0;
999 /* Close quoting. */
1000 if (quoted && buflen) {
1001 /* See if we need to close the quote by looking
1002 for an odd number of unescaped close quotes in
1003 the remainder of the display string. */
1004 int found_quote = 0, escaped = 0;
1005 char *c;
1006
1007 for (c = cp+1; *c; ++c) {
1008 if (*c == '\\') {
1009 escaped = ! escaped;
1010 } else {
1011 if (escaped) {
1012 escaped = 0;
1013 } else {
1014 if (*c == '\'') {
1015 found_quote = ! found_quote;
1016 }
1017 }
1018 }
1019 }
1020 if (! found_quote) {
1021 *bp++ = '\'';
1022 buflen--;
1023 quoted = false;
1024 }
1025 }
1026 }
1027 } else {
1028 raw:
1029 *bp++ = *cp;
1030 buflen--;
1031
1032 if (*cp == '\'')
1033 quoted = !quoted;
1034 }
1035
1036 *bp = '\0';
1037 }
1038
1039 if (buflen <= 0 ||
1040 (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
1041 /* content_error would provide a more useful error message
1042 * here, except that if we got overrun, it probably would
1043 * too.
1044 */
1045 return NOTOK;
1046 }
1047
1048 /* use charset string to modify display method */
1049 if (ct->c_termproc) {
1050 char term[BUFSIZ];
1051
1052 strncpy (term, buffer, sizeof(term));
1053 snprintf (buffer, buflen, ct->c_termproc, term);
1054 }
1055
1056 return OK;
1057 }
1058
1059
1060 int
1061 convert_charset (CT ct, char *dest_charset, int *message_mods)
1062 {
1063 char *src_charset = content_charset (ct);
1064 int status = OK;
1065
1066 if (strcasecmp (src_charset, dest_charset)) {
1067 #ifdef HAVE_ICONV
1068 iconv_t conv_desc = NULL;
1069 char *dest;
1070 int fd = -1;
1071 char **file = NULL;
1072 FILE **fp = NULL;
1073 size_t begin;
1074 size_t end;
1075 bool opened_input_file = false;
1076 char src_buffer[BUFSIZ];
1077 size_t dest_buffer_size = BUFSIZ;
1078 char *dest_buffer = mh_xmalloc(dest_buffer_size);
1079 HF hf;
1080 char *tempfile;
1081 int fromutf8 = !strcasecmp(src_charset, "UTF-8");
1082
1083 if ((conv_desc = iconv_open (dest_charset, src_charset)) ==
1084 (iconv_t) -1) {
1085 inform("Can't convert %s to %s", src_charset, dest_charset);
1086 free (src_charset);
1087 return NOTOK;
1088 }
1089
1090 if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
1091 die("unable to create temporary file in %s",
1092 get_temp_dir());
1093 }
1094 dest = mh_xstrdup(tempfile);
1095
1096 if (ct->c_cefile.ce_file) {
1097 file = &ct->c_cefile.ce_file;
1098 fp = &ct->c_cefile.ce_fp;
1099 begin = end = 0;
1100 } else if (ct->c_file) {
1101 file = &ct->c_file;
1102 fp = &ct->c_fp;
1103 begin = (size_t) ct->c_begin;
1104 end = (size_t) ct->c_end;
1105 } /* else no input file: shouldn't happen */
1106
1107 if (file && *file && fp) {
1108 if (! *fp) {
1109 if ((*fp = fopen (*file, "r")) == NULL) {
1110 advise (*file, "unable to open for reading");
1111 status = NOTOK;
1112 } else {
1113 opened_input_file = true;
1114 }
1115 }
1116 }
1117
1118 if (fp && *fp) {
1119 size_t inbytes;
1120 size_t bytes_to_read =
1121 end > 0 && end > begin ? end - begin : sizeof src_buffer;
1122
1123 fseeko (*fp, begin, SEEK_SET);
1124 while ((inbytes = fread (src_buffer, 1,
1125 min (bytes_to_read, sizeof src_buffer),
1126 *fp)) > 0) {
1127 ICONV_CONST char *ib = src_buffer;
1128 char *ob = dest_buffer;
1129 size_t outbytes = dest_buffer_size;
1130 size_t outbytes_before = outbytes;
1131
1132 if (end > 0) bytes_to_read -= inbytes;
1133
1134 iconv_start:
1135 if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) ==
1136 (size_t) -1) {
1137 if (errno == E2BIG) {
1138 /*
1139 * Bump up the buffer by at least a factor of 2
1140 * over what we need.
1141 */
1142 size_t bumpup = inbytes * 2, ob_off = ob - dest_buffer;
1143 dest_buffer_size += bumpup;
1144 dest_buffer = mh_xrealloc(dest_buffer,
1145 dest_buffer_size);
1146 ob = dest_buffer + ob_off;
1147 outbytes += bumpup;
1148 outbytes_before += bumpup;
1149 goto iconv_start;
1150 }
1151 if (errno == EINVAL) {
1152 /* middle of multi-byte sequence */
1153 if (write (fd, dest_buffer, outbytes_before - outbytes) < 0) {
1154 advise (dest, "write");
1155 }
1156 fseeko (*fp, -inbytes, SEEK_CUR);
1157 if (end > 0) bytes_to_read += inbytes;
1158 /* inform("convert_charset: EINVAL"); */
1159 continue;
1160 }
1161 if (errno == EILSEQ) {
1162 /* invalid multi-byte sequence */
1163 if (fromutf8) {
1164 for (++ib, --inbytes;
1165 inbytes > 0 &&
1166 (((unsigned char) *ib) & 0xc0) == 0x80;
1167 ++ib, --inbytes)
1168 continue;
1169 } else {
1170 ib++; inbytes--; /* skip it */
1171 }
1172 (*ob++) = '?'; outbytes --;
1173 /* inform("convert_charset: EILSEQ"); */
1174 goto iconv_start;
1175 }
1176 inform("convert_charset: errno = %d", errno);
1177 status = NOTOK;
1178 break;
1179 }
1180
1181 if (write (fd, dest_buffer, outbytes_before - outbytes) < 0) {
1182 advise (dest, "write");
1183 }
1184 }
1185
1186 if (opened_input_file) {
1187 fclose (*fp);
1188 *fp = NULL;
1189 }
1190 }
1191
1192 iconv_close (conv_desc);
1193 close (fd);
1194
1195 if (status == OK) {
1196 /* Replace the decoded file with the converted one. */
1197 if (ct->c_cefile.ce_file) {
1198 if (ct->c_cefile.ce_unlink) {
1199 (void) m_unlink (ct->c_cefile.ce_file);
1200 }
1201 free (ct->c_cefile.ce_file);
1202 }
1203 ct->c_cefile.ce_file = dest;
1204 ct->c_cefile.ce_unlink = 1;
1205
1206 ++*message_mods;
1207
1208 /* Update ct->c_ctline. */
1209 if (ct->c_ctline) {
1210 char *ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
1211 ct->c_ctinfo.ci_subtype, NULL);
1212 char *outline;
1213
1214 replace_param(&ct->c_ctinfo.ci_first_pm,
1215 &ct->c_ctinfo.ci_last_pm, "charset",
1216 dest_charset, 0);
1217 outline = output_params(LEN(TYPE_FIELD) + 1 + strlen(ctline),
1218 ct->c_ctinfo.ci_first_pm, NULL, 0);
1219 if (outline) {
1220 ctline = add(outline, ctline);
1221 free(outline);
1222 }
1223
1224 free (ct->c_ctline);
1225 ct->c_ctline = ctline;
1226 } /* else no CT line, which is odd */
1227
1228 /* Update Content-Type header field. */
1229 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1230 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1231 char *ctline = concat (ct->c_ctline, "\n", NULL);
1232
1233 free (hf->value);
1234 hf->value = ctline;
1235 break;
1236 }
1237 }
1238 } else {
1239 (void) m_unlink (dest);
1240 }
1241 free(dest_buffer);
1242 #else /* ! HAVE_ICONV */
1243 NMH_UNUSED (message_mods);
1244
1245 inform("Can't convert %s to %s without iconv", src_charset,
1246 dest_charset);
1247 errno = ENOSYS;
1248 status = NOTOK;
1249 #endif /* ! HAVE_ICONV */
1250 }
1251
1252 free (src_charset);
1253 return status;
1254 }
1255
1256
1257 static int
1258 convert_content_charset (CT ct, char **file)
1259 {
1260 int status = OK;
1261
1262 #ifdef HAVE_ICONV
1263 /* Using current locale, see if the content needs to be converted. */
1264
1265 /* content_charset() cannot return NULL. */
1266 char *src_charset = content_charset (ct);
1267
1268 if (! check_charset (src_charset, strlen (src_charset))) {
1269 int unused = 0;
1270
1271 char *dest_charset = getcpy (get_charset ());
1272
1273 if (convert_charset (ct, dest_charset, &unused) == 0) {
1274 *file = ct->c_cefile.ce_file;
1275 } else {
1276 status = NOTOK;
1277 }
1278
1279 free (dest_charset);
1280 }
1281 free (src_charset);
1282 #else /* ! HAVE_ICONV */
1283 NMH_UNUSED (ct);
1284 NMH_UNUSED (file);
1285 #endif /* ! HAVE_ICONV */
1286
1287 return status;
1288 }
1289
1290 /*
1291 * Compile our format string and save any parameters we care about.
1292 */
1293
1294 #define DEFAULT_HEADER "[ Message %{folder}%<{folder}:%>%(msg) ]"
1295 #define DEFAULT_MARKER "[ part %{part} - %{content-type} - " \
1296 "%<{description}%{description}" \
1297 "%?{cdispo-filename}%{cdispo-filename}" \
1298 "%|%{ctype-name}%> " \
1299 "%(kilo(size))B %<(unseen)\\(suppressed\\)%> ]"
1300
1301 static struct format *
1302 compile_header(char *form)
1303 {
1304 struct format *fmt;
1305 char *fmtstring;
1306 struct comp *comp = NULL;
1307 unsigned int bucket;
1308
1309 fmtstring = new_fs(form, NULL, DEFAULT_HEADER);
1310
1311 (void) fmt_compile(fmtstring, &fmt, 1);
1312 free_fs();
1313
1314 while ((comp = fmt_nextcomp(comp, &bucket)) != NULL) {
1315 if (strcasecmp(comp->c_name, "folder") == 0) {
1316 folder_comp = comp;
1317 }
1318 }
1319
1320 return fmt;
1321 }
1322
1323 static struct format *
1324 compile_marker(char *form)
1325 {
1326 struct format *fmt;
1327 char *fmtstring;
1328 struct comp *comp = NULL;
1329 unsigned int bucket;
1330 struct param_comp_list *pc_entry;
1331
1332 fmtstring = new_fs(form, NULL, DEFAULT_MARKER);
1333
1334 (void) fmt_compile(fmtstring, &fmt, 1);
1335 free_fs();
1336
1337 /*
1338 * Things we care about:
1339 *
1340 * part - Part name (e.g., 1.1)
1341 * content-type - Content-Type
1342 * description - Content-Description
1343 * disposition - Content-Disposition (inline, attachment)
1344 * ctype-<param> - Content-Type parameter
1345 * cdispo-<param> - Content-Disposition parameter
1346 */
1347
1348 while ((comp = fmt_nextcomp(comp, &bucket)) != NULL) {
1349 if (strcasecmp(comp->c_name, "part") == 0) {
1350 part_comp = comp;
1351 } else if (strcasecmp(comp->c_name, "content-type") == 0) {
1352 ctype_comp = comp;
1353 } else if (strcasecmp(comp->c_name, "description") == 0) {
1354 description_comp = comp;
1355 } else if (strcasecmp(comp->c_name, "disposition") == 0) {
1356 dispo_comp = comp;
1357 } else if (strncasecmp(comp->c_name, "ctype-", 6) == 0 &&
1358 strlen(comp->c_name) > 6) {
1359 NEW(pc_entry);
1360 pc_entry->param = mh_xstrdup(comp->c_name + 6);
1361 pc_entry->comp = comp;
1362 pc_entry->next = ctype_pc_list;
1363 ctype_pc_list = pc_entry;
1364 } else if (strncasecmp(comp->c_name, "cdispo-", 7) == 0 &&
1365 strlen(comp->c_name) > 7) {
1366 NEW(pc_entry);
1367 pc_entry->param = mh_xstrdup(comp->c_name + 7);
1368 pc_entry->comp = comp;
1369 pc_entry->next = dispo_pc_list;
1370 dispo_pc_list = pc_entry;
1371 }
1372 }
1373
1374 return fmt;
1375 }
1376
1377 /*
1378 * Output on stdout an appropriate marker for this content, using mh-format
1379 */
1380
1381 static void
1382 output_header(CT ct, struct format *fmt)
1383 {
1384 charstring_t outbuf = charstring_create (BUFSIZ);
1385 int dat[5] = { 0 };
1386 char *endp;
1387 int message = 0;
1388
1389 if (folder_comp)
1390 folder_comp->c_text = getcpy(folder);
1391
1392 if (ct->c_file && *ct->c_file) {
1393 message = strtol(ct->c_file, &endp, 10);
1394 if (*endp) message = 0;
1395 dat[0] = message;
1396 }
1397
1398 /* it would be nice to populate dat[2], for %(size) here,
1399 * but it's not available. it might also be nice to know
1400 * if the message originally had any mime parts or not -- but
1401 * there's also no record of that. (except for MIME-version:)
1402 */
1403
1404 fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
1405
1406 fputs(charstring_buffer (outbuf), stdout);
1407 charstring_free (outbuf);
1408
1409 fmt_freecomptext();
1410 }
1411
1412 static void
1413 output_marker(CT ct, struct format *fmt, int hidden)
1414 {
1415 charstring_t outbuf = charstring_create (BUFSIZ);
1416 struct param_comp_list *pcentry;
1417 int partsize;
1418 int message = 0;
1419 char *endp;
1420 int dat[5] = { 0 };
1421
1422 /*
1423 * Grab any items we care about.
1424 */
1425
1426 if (ctype_comp && ct->c_ctinfo.ci_type) {
1427 ctype_comp->c_text = concat(ct->c_ctinfo.ci_type, "/",
1428 ct->c_ctinfo.ci_subtype, NULL);
1429 }
1430
1431 if (part_comp && ct->c_partno) {
1432 part_comp->c_text = mh_xstrdup(ct->c_partno);
1433 }
1434
1435 if (description_comp && ct->c_descr) {
1436 description_comp->c_text = mh_xstrdup(ct->c_descr);
1437 }
1438
1439 if (dispo_comp && ct->c_dispo_type) {
1440 dispo_comp->c_text = mh_xstrdup(ct->c_dispo_type);
1441 }
1442
1443 for (pcentry = ctype_pc_list; pcentry != NULL; pcentry = pcentry->next) {
1444 pcentry->comp->c_text = get_param(ct->c_ctinfo.ci_first_pm,
1445 pcentry->param, '?', 0);
1446 }
1447
1448 for (pcentry = dispo_pc_list; pcentry != NULL; pcentry = pcentry->next) {
1449 pcentry->comp->c_text = get_param(ct->c_dispo_first,
1450 pcentry->param, '?', 0);
1451 }
1452
1453 if (ct->c_cesizefnx)
1454 partsize = (*ct->c_cesizefnx) (ct);
1455 else
1456 partsize = ct->c_end - ct->c_begin;
1457
1458 if (ct->c_file && *ct->c_file) {
1459 message = strtol(ct->c_file, &endp, 10);
1460 if (*endp) message = 0;
1461 dat[0] = message;
1462 }
1463 dat[2] = partsize;
1464
1465 /* make the part's hidden aspect available by overloading the
1466 * %(unseen) function. make the part's size available via %(size).
1467 * see comments in h/fmt_scan.h.
1468 */
1469 dat[4] = hidden;
1470
1471 fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
1472
1473 fputs(charstring_buffer (outbuf), stdout);
1474 charstring_free (outbuf);
1475
1476 fmt_freecomptext();
1477 }
1478
1479 /*
1480 * Reset (and free) any of the saved marker text
1481 */
1482
1483 static void
1484 free_markercomps(void)
1485 {
1486 struct param_comp_list *pc_entry, *pc2;
1487
1488 folder_comp = NULL;
1489 part_comp = NULL;
1490 ctype_comp = NULL;
1491 description_comp = NULL;
1492 dispo_comp = NULL;
1493
1494 for (pc_entry = ctype_pc_list; pc_entry != NULL; ) {
1495 free(pc_entry->param);
1496 pc2 = pc_entry->next;
1497 free(pc_entry);
1498 pc_entry = pc2;
1499 }
1500
1501 for (pc_entry = dispo_pc_list; pc_entry != NULL; ) {
1502 free(pc_entry->param);
1503 pc2 = pc_entry->next;
1504 free(pc_entry);
1505 pc_entry = pc2;
1506 }
1507 }
1508
1509 /*
1510 * Exit if the display process returned with a nonzero exit code, or terminated
1511 * with a SIGQUIT signal.
1512 */
1513
1514 static int
1515 pidcheck (int status)
1516 {
1517 if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
1518 return status;
1519
1520 fflush (stdout);
1521 fflush (stderr);
1522 done (1);
1523 return 1;
1524 }