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