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