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