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