]> diplodocus.org Git - nmh/blob - uip/mhfixmsg.c
c839b2469ab2a3295cb78e3380e2b7c1de78ff30
[nmh] / uip / mhfixmsg.c
1 /*
2 * mhfixmsg.c -- rewrite a message with various tranformations
3 *
4 * This code is Copyright (c) 2002 and 2013, by the authors of nmh.
5 * See the COPYRIGHT file in the root directory of the nmh
6 * distribution for complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/mime.h>
11 #include <h/mhparse.h>
12 #include <h/utils.h>
13 #include <h/signals.h>
14 #include <signal.h>
15 #include <fcntl.h>
16 #ifdef HAVE_ICONV
17 # include <iconv.h>
18 #endif
19
20 #define MHFIXMSG_SWITCHES \
21 X("decodetext 8bit|7bit", 0, DECODETEXTSW) \
22 X("nodecodetext", 0, NDECODETEXTSW) \
23 X("textcodeset", 0, TEXTCODESETSW) \
24 X("notextcodeset", 0, NTEXTCODESETSW) \
25 X("reformat", 0, REFORMATSW) \
26 X("noreformat", 0, NREFORMATSW) \
27 X("fixboundary", 0, FIXBOUNDARYSW) \
28 X("nofixboundary", 0, NFIXBOUNDARYSW) \
29 X("fixcte", 0, FIXCTESW) \
30 X("nofixcte", 0, NFIXCTESW) \
31 X("file file", 0, FILESW) \
32 X("outfile file", 0, OUTFILESW) \
33 X("rmmproc program", 0, RPROCSW) \
34 X("normmproc", 0, NRPRCSW) \
35 X("verbose", 0, VERBSW) \
36 X("noverbose", 0, NVERBSW) \
37 X("version", 0, VERSIONSW) \
38 X("help", 0, HELPSW) \
39
40 #define X(sw, minchars, id) id,
41 DEFINE_SWITCH_ENUM(MHFIXMSG);
42 #undef X
43
44 #define X(sw, minchars, id) { sw, minchars, id },
45 DEFINE_SWITCH_ARRAY(MHFIXMSG, switches);
46 #undef X
47
48
49 int verbosw;
50 int debugsw; /* Needed by mhparse.c. */
51
52 #define quitser pipeser
53
54 /* mhparse.c */
55 extern char *tmp; /* directory to place tmp files */
56 extern int skip_mp_cte_check; /* flag to InitMultiPart */
57 extern int suppress_bogus_mp_content_warning; /* flag to InitMultiPart */
58 extern int bogus_mp_content; /* flag from InitMultiPart */
59 CT parse_mime (char *);
60 void reverse_parts (CT);
61
62 /* mhoutsbr.c */
63 int output_message (CT, char *);
64
65 /* mhshowsbr.c */
66 int show_content_aux (CT, int, int, char *, char *);
67
68 /* mhmisc.c */
69 void flush_errors (void);
70
71 /* mhfree.c */
72 extern CT *cts;
73 void freects_done (int) NORETURN;
74
75 /*
76 * static prototypes
77 */
78 typedef struct fix_transformations {
79 int fixboundary;
80 int fixcte;
81 int reformat;
82 int decodetext;
83 char *textcodeset;
84 } fix_transformations;
85
86 int mhfixmsgsbr (CT *, const fix_transformations *, char *);
87 static void reverse_alternative_parts (CT);
88 static int fix_boundary (CT *, int *);
89 static int get_multipart_boundary (CT, char **);
90 static int replace_boundary (CT, char *, const char *);
91 static char *update_attr (char *, const char *, const char *e);
92 static int fix_multipart_cte (CT, int *);
93 static int set_ce (CT, int);
94 static int ensure_text_plain (CT *, CT, int *);
95 static CT build_text_plain_part (CT);
96 static CT divide_part (CT);
97 static void copy_ctinfo (CI, CI);
98 static int decode_part (CT);
99 static int reformat_part (CT, char *, char *, char *, int);
100 static int charset_encoding (CT);
101 static CT build_multipart_alt (CT, CT, int, int);
102 static int boundary_in_content (FILE **, char *, const char *);
103 static void transfer_noncontent_headers (CT, CT);
104 static int set_ct_type (CT, int type, int subtype, int encoding);
105 static int decode_text_parts (CT, int, int *);
106 static int content_encoding (CT);
107 static int convert_codesets (CT, char *, int *);
108 static int convert_codeset (CT, char *, int *);
109 static int write_content (CT, char *, char *, int, int);
110 static int remove_file (char *);
111 static void report (char *, char *, char *, ...);
112 static char *upcase (char *);
113 static void pipeser (int);
114
115
116 int
117 main (int argc, char **argv) {
118 int msgnum;
119 char *cp, *file = NULL, *folder = NULL;
120 char *maildir, buf[100], *outfile = NULL;
121 char **argp, **arguments;
122 struct msgs_array msgs = { 0, 0, NULL };
123 struct msgs *mp = NULL;
124 CT *ctp;
125 FILE *fp;
126 int using_stdin = 0;
127 int status = OK;
128 fix_transformations fx;
129 fx.reformat = fx.fixcte = fx.fixboundary = 1;
130 fx.decodetext = CE_8BIT;
131 fx.textcodeset = NULL;
132
133 done = freects_done;
134
135 #ifdef LOCALE
136 setlocale(LC_ALL, "");
137 #endif
138 invo_name = r1bindex (argv[0], '/');
139
140 /* read user profile/context */
141 context_read();
142
143 arguments = getarguments (invo_name, argc, argv, 1);
144 argp = arguments;
145
146 /*
147 * Parse arguments
148 */
149 while ((cp = *argp++)) {
150 if (*cp == '-') {
151 switch (smatch (++cp, switches)) {
152 case AMBIGSW:
153 ambigsw (cp, switches);
154 done (1);
155 case UNKWNSW:
156 adios (NULL, "-%s unknown", cp);
157
158 case HELPSW:
159 snprintf (buf, sizeof buf, "%s [+folder] [msgs] [switches]",
160 invo_name);
161 print_help (buf, switches, 1);
162 done (0);
163 case VERSIONSW:
164 print_version(invo_name);
165 done (0);
166
167 case DECODETEXTSW:
168 if (! (cp = *argp++) || *cp == '-')
169 adios (NULL, "missing argument to %s", argp[-2]);
170 if (! strcasecmp (cp, "8bit")) {
171 fx.decodetext = CE_8BIT;
172 } else if (! strcasecmp (cp, "7bit")) {
173 fx.decodetext = CE_7BIT;
174 } else {
175 adios (NULL, "invalid argument to %s", argp[-2]);
176 }
177 continue;
178 case NDECODETEXTSW:
179 fx.decodetext = 0;
180 continue;
181 case TEXTCODESETSW:
182 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
183 adios (NULL, "missing argument to %s", argp[-2]);
184 fx.textcodeset = cp;
185 continue;
186 case NTEXTCODESETSW:
187 fx.textcodeset = 0;
188 continue;
189 case FIXBOUNDARYSW:
190 fx.fixboundary = 1;
191 continue;
192 case NFIXBOUNDARYSW:
193 fx.fixboundary = 0;
194 continue;
195 case FIXCTESW:
196 fx.fixcte = 1;
197 continue;
198 case NFIXCTESW:
199 fx.fixcte = 0;
200 continue;
201 case REFORMATSW:
202 fx.reformat = 1;
203 continue;
204 case NREFORMATSW:
205 fx.reformat = 0;
206 continue;
207
208 case FILESW:
209 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
210 adios (NULL, "missing argument to %s", argp[-2]);
211 file = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
212 continue;
213
214 case OUTFILESW:
215 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
216 adios (NULL, "missing argument to %s", argp[-2]);
217 outfile = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
218 continue;
219
220 case RPROCSW:
221 if (!(rmmproc = *argp++) || *rmmproc == '-')
222 adios (NULL, "missing argument to %s", argp[-2]);
223 continue;
224 case NRPRCSW:
225 rmmproc = NULL;
226 continue;
227
228 case VERBSW:
229 verbosw = 1;
230 continue;
231 case NVERBSW:
232 verbosw = 0;
233 continue;
234 }
235 }
236 if (*cp == '+' || *cp == '@') {
237 if (folder)
238 adios (NULL, "only one folder at a time!");
239 else
240 folder = pluspath (cp);
241 } else
242 app_msgarg(&msgs, cp);
243 }
244
245 SIGNAL (SIGQUIT, quitser);
246 SIGNAL (SIGPIPE, pipeser);
247
248 /*
249 * Read the standard profile setup
250 */
251 if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) {
252 readconfig ((struct node **) 0, fp, cp, 0);
253 fclose (fp);
254 }
255
256 /*
257 * Check for storage directory. If specified,
258 * then store temporary files there. Else we
259 * store them in standard nmh directory.
260 */
261 if ((cp = context_find (nmhstorage)) && *cp)
262 tmp = concat (cp, "/", invo_name, NULL);
263 else
264 tmp = add (m_maildir (invo_name), NULL);
265
266 suppress_bogus_mp_content_warning = skip_mp_cte_check = 1;
267
268 if (! context_find ("path"))
269 free (path ("./", TFOLDER));
270
271 if (file && msgs.size)
272 adios (NULL, "cannot specify msg and file at same time!");
273
274 /*
275 * check if message is coming from file
276 */
277 if (file) {
278 /* If file is stdin, create a tmp file name before parse_mime()
279 has a chance, because it might put in on a different
280 filesystem than the output file. Instead, put it in the
281 user's preferred tmp directory. */
282 CT ct;
283
284 if (! strcmp ("-", file)) {
285 int fd;
286 char *cp;
287
288 using_stdin = 1;
289
290 if ((cp = m_mktemp2 (tmp, invo_name, &fd, NULL)) == NULL) {
291 adios (NULL, "unable to create temporary file");
292 } else {
293 free (file);
294 file = add (cp, NULL);
295 chmod (file, 0600);
296 cpydata (STDIN_FILENO, fd, "-", file);
297 }
298
299 if (close (fd)) {
300 unlink (file);
301 adios (NULL, "failed to write temporary file");
302 }
303 }
304
305 if (! (cts = (CT *) calloc ((size_t) 2, sizeof *cts)))
306 adios (NULL, "out of memory");
307 ctp = cts;
308
309 if ((ct = parse_mime (file))) *ctp++ = ct;
310 } else {
311 /*
312 * message(s) are coming from a folder
313 */
314 CT ct;
315
316 if (! msgs.size)
317 app_msgarg(&msgs, "cur");
318 if (! folder)
319 folder = getfolder (1);
320 maildir = m_maildir (folder);
321
322 if (chdir (maildir) == NOTOK)
323 adios (maildir, "unable to change directory to");
324
325 /* read folder and create message structure */
326 if (! (mp = folder_read (folder, 1)))
327 adios (NULL, "unable to read folder %s", folder);
328
329 /* check for empty folder */
330 if (mp->nummsg == 0)
331 adios (NULL, "no messages in %s", folder);
332
333 /* parse all the message ranges/sequences and set SELECTED */
334 for (msgnum = 0; msgnum < msgs.size; msgnum++)
335 if (! m_convert (mp, msgs.msgs[msgnum]))
336 done (1);
337 seq_setprev (mp); /* set the previous-sequence */
338
339 if (! (cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof *cts)))
340 adios (NULL, "out of memory");
341 ctp = cts;
342
343 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
344 if (is_selected(mp, msgnum)) {
345 char *msgnam;
346
347 msgnam = m_name (msgnum);
348 if ((ct = parse_mime (msgnam))) *ctp++ = ct;
349 }
350 }
351
352 seq_setcur (mp, mp->hghsel); /* update current message */
353 seq_save (mp); /* synchronize sequences */
354 context_replace (pfolder, folder);/* update current folder */
355 context_save (); /* save the context file */
356 }
357
358 if (*cts) {
359 for (ctp = cts; *ctp; ++ctp) {
360 status += mhfixmsgsbr (ctp, &fx, outfile);
361
362 if (using_stdin) {
363 unlink (file);
364
365 if (! outfile) {
366 /* Just calling m_backup() unlinks the backup file. */
367 (void) m_backup (file);
368 }
369 }
370 }
371 } else {
372 status = 1;
373 }
374
375 free (outfile);
376 free (tmp);
377 free (file);
378
379 /* done is freects_done, which will clean up all of cts. */
380 done (status);
381 return NOTOK;
382 }
383
384
385 int
386 mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) {
387 /* Store input filename in case one of the transformations, i.e.,
388 fix_boundary(), rewrites to a tmp file. */
389 char *input_filename = add ((*ctp)->c_file, NULL);
390 int modify_inplace = 0;
391 int message_mods = 0;
392 int status = OK;
393
394 if (outfile == NULL) {
395 modify_inplace = 1;
396
397 if ((*ctp)->c_file) {
398 outfile = add (m_mktemp2 (tmp, invo_name, NULL, NULL), NULL);
399 } else {
400 adios (NULL, "missing both input and output filenames\n");
401 }
402 }
403
404 reverse_alternative_parts (*ctp);
405 if (status == OK && fx->fixboundary) {
406 status = fix_boundary (ctp, &message_mods);
407 }
408 if (status == OK && fx->fixcte) {
409 status = fix_multipart_cte (*ctp, &message_mods);
410 }
411 if (status == OK && fx->reformat) {
412 status = ensure_text_plain (ctp, NULL, &message_mods);
413 }
414 if (status == OK && fx->decodetext) {
415 status = decode_text_parts (*ctp, fx->decodetext, &message_mods);
416 }
417 if (status == OK && fx->textcodeset != NULL) {
418 status = convert_codesets (*ctp, fx->textcodeset, &message_mods);
419 }
420
421 if (! (*ctp)->c_umask) {
422 /* Set the umask for the contents file. This currently
423 isn't used but just in case it is in the future. */
424 struct stat st;
425
426 if (stat ((*ctp)->c_file, &st) != NOTOK) {
427 (*ctp)->c_umask = ~(st.st_mode & 0777);
428 } else {
429 (*ctp)->c_umask = ~m_gmprot();
430 }
431 }
432
433 /*
434 * Write the content to a file
435 */
436 if (status == OK) {
437 status = write_content (*ctp, input_filename, outfile, modify_inplace,
438 message_mods);
439 } else if (! modify_inplace) {
440 /* Something went wrong. Output might be expected, such
441 as if this were run as a filter. Just copy the input
442 to the output. */
443 int in = open (input_filename, O_RDONLY);
444 int out = strcmp (outfile, "-")
445 ? open (outfile, O_WRONLY | O_CREAT, m_gmprot ())
446 : STDOUT_FILENO;
447
448 if (in != -1 && out != -1) {
449 cpydata (in, out, input_filename, outfile);
450 } else {
451 status = NOTOK;
452 }
453
454 close (out);
455 close (in);
456 }
457
458 if (modify_inplace) {
459 if (status != OK) unlink (outfile);
460 free (outfile);
461 outfile = NULL;
462 }
463
464 free (input_filename);
465
466 return status;
467 }
468
469
470 /* parse_mime() arranges alternates in reverse (priority) order, so
471 reverse them back. This will put a text/plain part at the front of
472 a multipart/alternative part, for example, where it belongs. */
473 static void
474 reverse_alternative_parts (CT ct) {
475 if (ct->c_type == CT_MULTIPART) {
476 struct multipart *m = (struct multipart *) ct->c_ctparams;
477 struct part *part;
478
479 if (ct->c_subtype == MULTI_ALTERNATE) {
480 reverse_parts (ct);
481 }
482
483 /* And call recursively on each part of a multipart. */
484 for (part = m->mp_parts; part; part = part->mp_next) {
485 reverse_alternative_parts (part->mp_part);
486 }
487 }
488 }
489
490
491 static int
492 fix_boundary (CT *ct, int *message_mods) {
493 struct multipart *mp;
494 int status = OK;
495
496 if (bogus_mp_content) {
497 mp = (struct multipart *) (*ct)->c_ctparams;
498
499 /*
500 * 1) Get boundary at end of part.
501 * 2) Get boundary at beginning of part and compare to the end-of-part
502 * boundary.
503 * 3) Write out contents of ct to tmp file, replacing boundary in
504 * header with boundary from part. Set c_unlink to 1.
505 * 4) Free ct.
506 * 5) Call parse_mime() on the tmp file, replacing ct.
507 */
508
509 if (mp && mp->mp_start) {
510 char *part_boundary;
511
512 if (get_multipart_boundary (*ct, &part_boundary) == OK) {
513 char *fixed;
514
515 if ((fixed = m_mktemp2 (tmp, invo_name, NULL, &(*ct)->c_fp))) {
516 if (replace_boundary (*ct, fixed, part_boundary) == OK) {
517 char *filename = add ((*ct)->c_file, NULL);
518
519 free_content (*ct);
520 if ((*ct = parse_mime (fixed))) {
521 (*ct)->c_unlink = 1;
522
523 ++*message_mods;
524 if (verbosw) {
525 report (NULL, filename,
526 "fix multipart boundary");
527 }
528 }
529 free (filename);
530 } else {
531 advise (NULL, "unable to replace broken boundary");
532 status = NOTOK;
533 }
534 } else {
535 advise (NULL, "unable to create temporary file");
536 status = NOTOK;
537 }
538
539 free (part_boundary);
540 }
541 }
542 }
543
544 return status;
545 }
546
547
548 static int
549 get_multipart_boundary (CT ct, char **part_boundary) {
550 char buffer[BUFSIZ];
551 char *end_boundary = NULL;
552 off_t begin = (off_t) ct->c_end > (off_t) (ct->c_begin + sizeof buffer)
553 ? (off_t) (ct->c_end - sizeof buffer)
554 : (off_t) ct->c_begin;
555 size_t bytes_read;
556 int status = OK;
557
558 /* This will fail if the boundary spans fread() calls. BUFSIZ should
559 be big enough, even if it's just 1024, to make that unlikely. */
560
561 /* free_content() will close ct->c_fp. */
562 if (! ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
563 advise (ct->c_file, "unable to open for reading");
564 return NOTOK;
565 }
566
567 /* Get boundary at end of multipart. */
568 while (begin >= (off_t) ct->c_begin) {
569 fseeko (ct->c_fp, begin, SEEK_SET);
570 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
571 char *end = buffer + bytes_read - 1;
572 char *cp;
573
574 if ((cp = rfind_str (buffer, bytes_read, "--"))) {
575 /* Trim off trailing "--" and anything beyond. */
576 *cp-- = '\0';
577 if ((end = rfind_str (buffer, cp - buffer, "\n"))) {
578 if (strlen (end) > 3 && *end++ == '\n' &&
579 *end++ == '-' && *end++ == '-') {
580 end_boundary = add (end, NULL);
581 break;
582 }
583 }
584 }
585 }
586
587 if (! end_boundary && begin > (off_t) (ct->c_begin + sizeof buffer)) {
588 begin -= sizeof buffer;
589 } else {
590 break;
591 }
592 }
593
594 /* Get boundary at beginning of multipart. */
595 if (end_boundary) {
596 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
597 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
598 if (bytes_read >= strlen (end_boundary)) {
599 char *cp = find_str (buffer, bytes_read, end_boundary);
600
601 if (cp && cp - buffer >= 2 && *--cp == '-' &&
602 *--cp == '-' && (cp > buffer && *--cp == '\n')) {
603 status = OK;
604 break;
605 }
606 } else {
607 /* The start and end boundaries didn't match, or the
608 start boundary doesn't begin with "\n--" (or "--"
609 if at the beginning of buffer). Keep trying. */
610 status = NOTOK;
611 }
612 }
613 } else {
614 status = NOTOK;
615 }
616
617 if (status == OK) {
618 *part_boundary = end_boundary;
619 } else {
620 *part_boundary = NULL;
621 free (end_boundary);
622 }
623
624 return status;
625 }
626
627
628 /* Open and copy ct->c_file to file, replacing the multipart boundary. */
629 static int
630 replace_boundary (CT ct, char *file, const char *boundary) {
631 FILE *fpin, *fpout;
632 int compnum, state;
633 char buf[BUFSIZ], name[NAMESZ];
634 char *np, *vp;
635 m_getfld_state_t gstate = 0;
636 int status = OK;
637
638 if (ct->c_file == NULL) {
639 advise (NULL, "missing input filename");
640 return NOTOK;
641 }
642
643 if ((fpin = fopen (ct->c_file, "r")) == NULL) {
644 advise (ct->c_file, "unable to open for reading");
645 return NOTOK;
646 }
647
648 if ((fpout = fopen (file, "w")) == NULL) {
649 fclose (fpin);
650 advise (file, "unable to open for writing");
651 return NOTOK;
652 }
653
654 for (compnum = 1;;) {
655 int bufsz = (int) sizeof buf;
656
657 switch (state = m_getfld (&gstate, name, buf, &bufsz, fpin)) {
658 case FLD:
659 case FLDPLUS:
660 compnum++;
661
662 /* get copies of the buffers */
663 np = add (name, NULL);
664 vp = add (buf, NULL);
665
666 /* if necessary, get rest of field */
667 while (state == FLDPLUS) {
668 bufsz = sizeof buf;
669 state = m_getfld (&gstate, name, buf, &bufsz, fpin);
670 vp = add (buf, vp); /* add to previous value */
671 }
672
673 if (strcasecmp (TYPE_FIELD, np)) {
674 fprintf (fpout, "%s:%s", np, vp);
675 } else {
676 char *new_boundary = update_attr (vp, "boundary=", boundary);
677
678 fprintf (fpout, "%s:%s\n", np, new_boundary);
679 free (new_boundary);
680 }
681
682 free (vp);
683 free (np);
684
685 continue;
686
687 case BODY:
688 fputs ("\n", fpout);
689 /* buf will have a terminating NULL, skip it. */
690 fwrite (buf, 1, bufsz-1, fpout);
691 continue;
692
693 case FILEEOF:
694 break;
695
696 case LENERR:
697 case FMTERR:
698 advise (NULL, "message format error in component #%d", compnum);
699 status = NOTOK;
700 break;
701
702 default:
703 advise (NULL, "getfld() returned %d", state);
704 status = NOTOK;
705 break;
706 }
707
708 break;
709 }
710
711 m_getfld_state_destroy (&gstate);
712 fclose (fpout);
713 fclose (fpin);
714
715 return status;
716 }
717
718
719 /* Change the value of a name=value pair in a header field body.
720 If the name isn't there, append them. In any case, a new
721 string will be allocated and must be free'd by the caller.
722 Trims any trailing newlines. */
723 static char *
724 update_attr (char *body, const char *name, const char *value) {
725 char *bp = nmh_strcasestr (body, name);
726 char *new_body;
727
728 if (bp) {
729 char *other_attrs = strchr (bp, ';');
730
731 *(bp + strlen (name)) = '\0';
732 new_body = concat (body, "\"", value, "\"", NULL);
733
734 if (other_attrs) {
735 char *cp;
736
737 /* Trim any trailing newlines. */
738 for (cp = &other_attrs[strlen (other_attrs) - 1];
739 cp > other_attrs && *cp == '\n';
740 *cp-- = '\0') continue;
741 new_body = add (other_attrs, new_body);
742 }
743 } else {
744 char *cp;
745
746 /* Append name/value pair, after first removing a final newline
747 and (extraneous) semicolon. */
748 if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0';
749 if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0';
750 new_body = concat (body, "; ", name, "\"", value, "\"", NULL);
751 }
752
753 return new_body;
754 }
755
756
757 static int
758 fix_multipart_cte (CT ct, int *message_mods) {
759 int status = OK;
760
761 if (ct->c_type == CT_MULTIPART) {
762 struct multipart *m;
763 struct part *part;
764
765 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT &&
766 ct->c_encoding != CE_BINARY) {
767 HF hf;
768
769 for (hf = ct->c_first_hf; hf; hf = hf->next) {
770 char *name = hf->name;
771 for (; *name && isspace ((unsigned char) *name); ++name) {
772 continue;
773 }
774
775 if (! strncasecmp (name, ENCODING_FIELD,
776 strlen (ENCODING_FIELD))) {
777 char *prefix = "Nmh-REPLACED-INVALID-";
778 HF h = mh_xmalloc (sizeof *h);
779
780 h->name = add (hf->name, NULL);
781 h->hf_encoding = hf->hf_encoding;
782 h->next = hf->next;
783 hf->next = h;
784
785 /* Retain old header but prefix its name. */
786 free (hf->name);
787 hf->name = concat (prefix, h->name, NULL);
788
789 ++*message_mods;
790 if (verbosw) {
791 char *encoding = cpytrim (hf->value);
792 report (ct->c_partno, ct->c_file,
793 "replace Content-Transfer-Encoding of %s "
794 "with 8 bit", encoding);
795 free (encoding);
796 }
797
798 h->value = add (" 8bit\n", NULL);
799
800 /* Don't need to warn for multiple C-T-E header
801 fields, parse_mime() already does that. But
802 if there are any, fix them all as necessary. */
803 hf = h;
804 }
805 }
806
807 set_ce (ct, CE_8BIT);
808 }
809
810 m = (struct multipart *) ct->c_ctparams;
811 for (part = m->mp_parts; part; part = part->mp_next) {
812 if (fix_multipart_cte (part->mp_part, message_mods) != OK) {
813 status = NOTOK;
814 break;
815 }
816 }
817 }
818
819 return status;
820 }
821
822
823 static int
824 set_ce (CT ct, int encoding) {
825 const char *ce = ce_str (encoding);
826 const struct str2init *ctinit = get_ce_method (ce);
827
828 if (ctinit) {
829 char *cte = concat (" ", ce, "\n", NULL);
830 int found_cte = 0;
831 HF hf;
832 /* Decoded contents might be in ct->c_cefile.ce_file, if the
833 caller is decode_text_parts (). Save because we'll
834 overwrite below. */
835 struct cefile decoded_content_info = ct->c_cefile;
836
837 ct->c_encoding = encoding;
838
839 ct->c_ctinitfnx = ctinit->si_init;
840 /* This will assign ct->c_cefile with an all-0 struct, which
841 is what we want. */
842 (*ctinit->si_init) (ct);
843 /* After returning, the caller should set
844 ct->c_cefile.ce_file to the name of the file containing
845 the contents. */
846
847 /* Restore the cefile. */
848 ct->c_cefile = decoded_content_info;
849
850 /* Update/add Content-Transfer-Encoding header field. */
851 for (hf = ct->c_first_hf; hf; hf = hf->next) {
852 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
853 found_cte = 1;
854 free (hf->value);
855 hf->value = cte;
856 }
857 }
858 if (! found_cte) {
859 add_header (ct, add (ENCODING_FIELD, NULL), cte);
860 }
861
862 /* Update c_celine. It's used only by mhlist -debug. */
863 free (ct->c_celine);
864 ct->c_celine = add (cte, NULL);
865
866 return OK;
867 } else {
868 return NOTOK;
869 }
870 }
871
872
873 /* Make sure each text part has a corresponding text/plain part. */
874 static int
875 ensure_text_plain (CT *ct, CT parent, int *message_mods) {
876 int status = OK;
877
878 switch ((*ct)->c_type) {
879 case CT_TEXT: {
880 int has_text_plain = 0;
881
882 /* Nothing to do for text/plain. */
883 if ((*ct)->c_subtype == TEXT_PLAIN) return OK;
884
885 if (parent && parent->c_type == CT_MULTIPART &&
886 parent->c_subtype == MULTI_ALTERNATE) {
887 struct multipart *mp = (struct multipart *) parent->c_ctparams;
888 struct part *part;
889 int new_subpart_number = 1;
890
891 /* See if there is a sibling text/plain. */
892 for (part = mp->mp_parts; part; part = part->mp_next) {
893 ++new_subpart_number;
894 if (part->mp_part->c_type == CT_TEXT &&
895 part->mp_part->c_subtype == TEXT_PLAIN) {
896 has_text_plain = 1;
897 break;
898 }
899 }
900
901 if (! has_text_plain) {
902 /* Parent is a multipart/alternative. Insert a new
903 text/plain subpart. */
904 struct part *new_part = mh_xmalloc (sizeof *new_part);
905
906 if ((new_part->mp_part = build_text_plain_part (*ct))) {
907 char buffer[16];
908 snprintf (buffer, sizeof buffer, "%d", new_subpart_number);
909
910 new_part->mp_next = mp->mp_parts;
911 mp->mp_parts = new_part;
912 new_part->mp_part->c_partno =
913 concat (parent->c_partno ? parent->c_partno : "1", ".",
914 buffer, NULL);
915
916 ++*message_mods;
917 if (verbosw) {
918 report (parent->c_partno, parent->c_file,
919 "insert text/plain part");
920 }
921 } else {
922 free_content (new_part->mp_part);
923 free (new_part);
924 status = NOTOK;
925 }
926 }
927 } else {
928 /* Slip new text/plain part into a new multipart/alternative. */
929 CT tp_part = build_text_plain_part (*ct);
930
931 if (tp_part) {
932 CT mp_alt = build_multipart_alt (*ct, tp_part, CT_MULTIPART,
933 MULTI_ALTERNATE);
934 if (mp_alt) {
935 struct multipart *mp =
936 (struct multipart *) mp_alt->c_ctparams;
937
938 if (mp && mp->mp_parts) {
939 mp->mp_parts->mp_part = tp_part;
940 /* Make the new multipart/alternative the parent. */
941 *ct = mp_alt;
942
943 ++*message_mods;
944 if (verbosw) {
945 report ((*ct)->c_partno, (*ct)->c_file,
946 "insert text/plain part");
947 }
948 } else {
949 free_content (tp_part);
950 free_content (mp_alt);
951 status = NOTOK;
952 }
953 } else {
954 status = NOTOK;
955 }
956 } else {
957 status = NOTOK;
958 }
959 }
960 break;
961 }
962
963 case CT_MULTIPART: {
964 struct multipart *mp = (struct multipart *) (*ct)->c_ctparams;
965 struct part *part;
966
967 for (part = mp->mp_parts; status == OK && part; part = part->mp_next) {
968 if ((*ct)->c_type == CT_MULTIPART) {
969 status = ensure_text_plain (&part->mp_part, *ct, message_mods);
970 }
971 }
972 break;
973 }
974
975 case CT_MESSAGE:
976 if ((*ct)->c_subtype == MESSAGE_EXTERNAL) {
977 struct exbody *e;
978
979 e = (struct exbody *) (*ct)->c_ctparams;
980 status = ensure_text_plain (&e->eb_content, *ct, message_mods);
981 }
982 break;
983 }
984
985 return status;
986 }
987
988
989 static CT
990 build_text_plain_part (CT encoded_part) {
991 CT tp_part = divide_part (encoded_part);
992 char *tmp_plain_file = NULL;
993
994 if (decode_part (tp_part) == OK) {
995 /* Now, tp_part->c_cefile.ce_file is the name of the tmp file that
996 contains the decoded contents. And the decoding function, such
997 as openQuoted, will have set ...->ce_unlink to 1 so that it will
998 be unlinked by free_content (). */
999 tmp_plain_file = add (m_mktemp2 (tmp, invo_name, NULL, NULL), NULL);
1000 if (reformat_part (tp_part, tmp_plain_file,
1001 tp_part->c_ctinfo.ci_type,
1002 tp_part->c_ctinfo.ci_subtype,
1003 tp_part->c_type) == OK) {
1004 return tp_part;
1005 }
1006 }
1007
1008 free_content (tp_part);
1009 unlink (tmp_plain_file);
1010 free (tmp_plain_file);
1011
1012 return NULL;
1013 }
1014
1015
1016 static CT
1017 divide_part (CT ct) {
1018 CT new_part;
1019
1020 if ((new_part = (CT) calloc (1, sizeof *new_part)) == NULL)
1021 adios (NULL, "out of memory");
1022
1023 /* Just copy over what is needed for decoding. c_vrsn and
1024 c_celine aren't necessary. */
1025 new_part->c_file = add (ct->c_file, NULL);
1026 new_part->c_begin = ct->c_begin;
1027 new_part->c_end = ct->c_end;
1028 copy_ctinfo (&new_part->c_ctinfo, &ct->c_ctinfo);
1029 new_part->c_type = ct->c_type;
1030 new_part->c_cefile = ct->c_cefile;
1031 new_part->c_encoding = ct->c_encoding;
1032 new_part->c_ctinitfnx = ct->c_ctinitfnx;
1033 new_part->c_ceopenfnx = ct->c_ceopenfnx;
1034 new_part->c_ceclosefnx = ct->c_ceclosefnx;
1035 new_part->c_cesizefnx = ct->c_cesizefnx;
1036
1037 /* c_ctline is used by reformat__part(), so it can preserve
1038 anything after the type/subtype. */
1039 new_part->c_ctline = add (ct->c_ctline, NULL);
1040
1041 return new_part;
1042 }
1043
1044
1045 static void
1046 copy_ctinfo (CI dest, CI src) {
1047 char **s_ap, **d_ap, **s_vp, **d_vp;
1048
1049 dest->ci_type = src->ci_type ? add (src->ci_type, NULL) : NULL;
1050 dest->ci_subtype = src->ci_subtype ? add (src->ci_subtype, NULL) : NULL;
1051
1052 for (s_ap = src->ci_attrs, d_ap = dest->ci_attrs,
1053 s_vp = src->ci_values, d_vp = dest->ci_values;
1054 *s_ap;
1055 ++s_ap, ++d_ap, ++s_vp, ++d_vp) {
1056 *d_ap = add (*s_ap, NULL);
1057 *d_vp = *s_vp;
1058 }
1059 *d_ap = NULL;
1060
1061 dest->ci_comment = src->ci_comment ? add (src->ci_comment, NULL) : NULL;
1062 dest->ci_magic = src->ci_magic ? add (src->ci_magic, NULL) : NULL;
1063 }
1064
1065
1066 static int
1067 decode_part (CT ct) {
1068 char *tmp_decoded;
1069 int status;
1070
1071 tmp_decoded = add (m_mktemp2 (tmp, invo_name, NULL, NULL), NULL);
1072 /* The following call will load ct->c_cefile.ce_file with the tmp
1073 filename of the decoded content. tmp_decoded will contain the
1074 encoded output, get rid of that. */
1075 status = output_message (ct, tmp_decoded);
1076 unlink (tmp_decoded);
1077 free (tmp_decoded);
1078
1079 return status;
1080 }
1081
1082
1083 /* Some of the arguments aren't really needed now, but maybe will
1084 be in the future for other than text types. */
1085 static int
1086 reformat_part (CT ct, char *file, char *type, char *subtype, int c_type) {
1087 int output_subtype, output_encoding;
1088 char *cp, *cf;
1089 int status;
1090
1091 /* Hacky: this redirects the output from whatever command is used
1092 to show the part to a file. So, the user can't have any output
1093 redirection in that command.
1094 Could show_multi() in mhshowsbr.c avoid this? */
1095
1096 /* Check for invo_name-format-type/subtype. */
1097 cp = concat (invo_name, "-format-", type, "/", subtype, NULL);
1098 if ((cf = context_find (cp)) && *cf != '\0') {
1099 if (strchr (cf, '>')) {
1100 free (cp);
1101 advise (NULL, "'>' prohibited in \"%s\",\nplease fix your "
1102 "%s-format-%s/%s profile entry", cf, invo_name, type,
1103 subtype);
1104 return NOTOK;
1105 }
1106 } else {
1107 free (cp);
1108
1109 /* Check for invo_name-format-type. */
1110 cp = concat (invo_name, "-format-", type, NULL);
1111 if (! (cf = context_find (cp)) || *cf == '\0') {
1112 free (cp);
1113 if (verbosw) {
1114 advise (NULL, "Don't know how to convert %s, there is no "
1115 "%s-format-%s/%s profile entry",
1116 ct->c_file, invo_name, type, subtype);
1117 }
1118 return NOTOK;
1119 }
1120
1121 if (strchr (cf, '>')) {
1122 free (cp);
1123 advise (NULL, "'>' prohibited in \"%s\"", cf);
1124 return NOTOK;
1125 }
1126 }
1127 free (cp);
1128
1129 cp = concat (cf, " >", file, NULL);
1130 status = show_content_aux (ct, 1, 0, cp, NULL);
1131 free (cp);
1132
1133 /* Unlink decoded content tmp file and free its filename to avoid
1134 leaks. The file stream should already have been closed. */
1135 if (ct->c_cefile.ce_unlink) {
1136 unlink (ct->c_cefile.ce_file);
1137 free (ct->c_cefile.ce_file);
1138 ct->c_cefile.ce_file = NULL;
1139 ct->c_cefile.ce_unlink = 0;
1140 }
1141
1142 if (c_type == CT_TEXT) {
1143 output_subtype = TEXT_PLAIN;
1144 } else {
1145 /* Set subtype to 0, which is always an UNKNOWN subtype. */
1146 output_subtype = 0;
1147 }
1148 output_encoding = charset_encoding (ct);
1149
1150 if (set_ct_type (ct, c_type, output_subtype, output_encoding) == OK) {
1151 ct->c_cefile.ce_file = file;
1152 ct->c_cefile.ce_unlink = 1;
1153 } else {
1154 ct->c_cefile.ce_unlink = 0;
1155 status = NOTOK;
1156 }
1157
1158 return status;
1159 }
1160
1161
1162 /* Identifies 7bit or 8bit content based on charset, if specified. */
1163 static int
1164 charset_encoding (CT ct) {
1165 int encoding = CE_8BIT;
1166 CI ctinfo = &ct->c_ctinfo;
1167 char **ap, **vp;
1168
1169 for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) {
1170 if (! strcasecmp (*ap, "charset")) {
1171 /* norm_charmap() is case sensitive. */
1172 char *ch = upcase (*vp);
1173
1174 if (! strcmp (norm_charmap (ch), "US-ASCII")) encoding = CE_7BIT;
1175 free (ch);
1176 break;
1177 }
1178 }
1179
1180 return encoding;
1181 }
1182
1183
1184 static CT
1185 build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) {
1186 char *boundary_prefix = "----=_nmh-multipart";
1187 char *boundary = concat (boundary_prefix, first_alt->c_partno, NULL);
1188 char *boundary_indicator = "; boundary=";
1189 char *typename, *subtypename, *name;
1190 CT ct;
1191 struct part *p;
1192 struct multipart *m;
1193 char *cp;
1194 const struct str2init *ctinit;
1195
1196 if ((ct = (CT) calloc (1, sizeof *ct)) == NULL)
1197 adios (NULL, "out of memory");
1198
1199 /* Set up the multipart/alternative part. These fields of *ct were
1200 initialized to 0 by calloc():
1201 c_fp, c_unlink, c_begin, c_end,
1202 c_vrsn, c_ctline, c_celine,
1203 c_id, c_descr, c_dispo, c_partno,
1204 c_ctinfo.ci_comment, c_ctinfo.ci_magic,
1205 c_cefile, c_encoding,
1206 c_digested, c_digest[16], c_ctexbody,
1207 c_ctinitfnx, c_ceopenfnx, c_ceclosefnx, c_cesizefnx,
1208 c_umask, c_pid, c_rfc934,
1209 c_showproc, c_termproc, c_storeproc, c_storage, c_folder
1210 */
1211
1212 ct->c_file = add (first_alt->c_file, NULL);
1213 ct->c_type = type;
1214 ct->c_subtype = subtype;
1215
1216 ctinit = get_ct_init (ct->c_type);
1217
1218 typename = ct_type_str (type);
1219 subtypename = ct_subtype_str (type, subtype);
1220
1221 {
1222 int serial = 0;
1223 int found_boundary = 1;
1224
1225 while (found_boundary && serial < 1000000) {
1226 found_boundary = 0;
1227
1228 /* Ensure that the boundary doesn't appear in the decoded
1229 content. */
1230 if (new_part->c_cefile.ce_file) {
1231 if ((found_boundary =
1232 boundary_in_content (&new_part->c_cefile.ce_fp,
1233 new_part->c_cefile.ce_file,
1234 boundary)) == -1) {
1235 return NULL;
1236 }
1237 }
1238
1239 /* Ensure that the boundary doesn't appear in the encoded
1240 content. */
1241 if (! found_boundary && new_part->c_file) {
1242 if ((found_boundary = boundary_in_content (&new_part->c_fp,
1243 new_part->c_file,
1244 boundary)) == -1) {
1245 return NULL;
1246 }
1247 }
1248
1249 if (found_boundary) {
1250 /* Try a slightly different boundary. */
1251 char buffer2[16];
1252
1253 free (boundary);
1254 ++serial;
1255 snprintf (buffer2, sizeof buffer2, "%d", serial);
1256 boundary =
1257 concat (boundary_prefix,
1258 first_alt->c_partno ? first_alt->c_partno : "",
1259 "-", buffer2, NULL);
1260 }
1261 }
1262
1263 if (found_boundary) {
1264 advise (NULL, "giving up trying to find a unique boundary");
1265 return NULL;
1266 }
1267 }
1268
1269 name = concat (" ", typename, "/", subtypename, boundary_indicator, "\"",
1270 boundary, "\"", NULL);
1271
1272 /* Load c_first_hf and c_last_hf. */
1273 transfer_noncontent_headers (first_alt, ct);
1274 add_header (ct, add (TYPE_FIELD, NULL), concat (name, "\n", NULL));
1275 free (name);
1276
1277 /* Load c_partno. */
1278 if (first_alt->c_partno) {
1279 ct->c_partno = add (first_alt->c_partno, NULL);
1280 free (first_alt->c_partno);
1281 first_alt->c_partno = concat (ct->c_partno, ".1", NULL);
1282 new_part->c_partno = concat (ct->c_partno, ".2", NULL);
1283 } else {
1284 first_alt->c_partno = add ("1", NULL);
1285 new_part->c_partno = add ("2", NULL);
1286 }
1287
1288 if (ctinit) {
1289 ct->c_ctinfo.ci_type = add (typename, NULL);
1290 ct->c_ctinfo.ci_subtype = add (subtypename, NULL);
1291 }
1292
1293 name = concat (" ", typename, "/", subtypename, boundary_indicator,
1294 boundary, NULL);
1295 if ((cp = strstr (name, boundary_indicator))) {
1296 ct->c_ctinfo.ci_attrs[0] = name;
1297 ct->c_ctinfo.ci_attrs[1] = NULL;
1298 /* ci_values don't get free'd, so point into ci_attrs. */
1299 ct->c_ctinfo.ci_values[0] = cp + strlen (boundary_indicator);
1300 }
1301
1302 p = (struct part *) mh_xmalloc (sizeof *p);
1303 p->mp_next = (struct part *) mh_xmalloc (sizeof *p->mp_next);
1304 p->mp_next->mp_next = NULL;
1305 p->mp_next->mp_part = first_alt;
1306
1307 if ((m = (struct multipart *) calloc (1, sizeof (struct multipart))) ==
1308 NULL)
1309 adios (NULL, "out of memory");
1310 m->mp_start = concat (boundary, "\n", NULL);
1311 m->mp_stop = concat (boundary, "--\n", NULL);
1312 m->mp_parts = p;
1313 ct->c_ctparams = (void *) m;
1314
1315 free (boundary);
1316
1317 return ct;
1318 }
1319
1320
1321 /* Check that the boundary does not appear in the content. */
1322 static int
1323 boundary_in_content (FILE **fp, char *file, const char *boundary) {
1324 char buffer[BUFSIZ];
1325 size_t bytes_read;
1326 int found_boundary = 0;
1327
1328 /* free_content() will close *fp if we fopen it here. */
1329 if (! *fp && (*fp = fopen (file, "r")) == NULL) {
1330 advise (file, "unable to open %s for reading", file);
1331 return NOTOK;
1332 }
1333
1334 fseeko (*fp, 0L, SEEK_SET);
1335 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) > 0) {
1336 if (find_str (buffer, bytes_read, boundary)) {
1337 found_boundary = 1;
1338 break;
1339 }
1340 }
1341
1342 return found_boundary;
1343 }
1344
1345
1346 /* Remove all non-Content headers. */
1347 static void
1348 transfer_noncontent_headers (CT old, CT new) {
1349 HF hp, hp_prev;
1350
1351 hp_prev = hp = old->c_first_hf;
1352 while (hp) {
1353 HF next = hp->next;
1354
1355 if (strncasecmp (XXX_FIELD_PRF, hp->name, strlen (XXX_FIELD_PRF))) {
1356 if (hp == old->c_last_hf) {
1357 if (hp == old->c_first_hf) {
1358 old->c_last_hf = old->c_first_hf = NULL;
1359 } else {
1360 hp_prev->next = NULL;
1361 old->c_last_hf = hp_prev;
1362 }
1363 } else {
1364 if (hp == old->c_first_hf) {
1365 old->c_first_hf = next;
1366 } else {
1367 hp_prev->next = next;
1368 }
1369 }
1370
1371 /* Put node hp in the new CT. */
1372 if (new->c_first_hf == NULL) {
1373 new->c_first_hf = hp;
1374 } else {
1375 new->c_last_hf->next = hp;
1376 }
1377 new->c_last_hf = hp;
1378 } else {
1379 /* A Content- header, leave in old. */
1380 hp_prev = hp;
1381 }
1382
1383 hp = next;
1384 }
1385 }
1386
1387
1388 static int
1389 set_ct_type (CT ct, int type, int subtype, int encoding) {
1390 char *typename = ct_type_str (type);
1391 char *subtypename = ct_subtype_str (type, subtype);
1392 /* E.g, " text/plain" */
1393 char *type_subtypename = concat (" ", typename, "/", subtypename, NULL);
1394 /* E.g, " text/plain\n" */
1395 char *name_plus_nl = concat (type_subtypename, "\n", NULL);
1396 int found_content_type = 0;
1397 HF hf;
1398 const char *cp = NULL;
1399 char *ctline;
1400 int status;
1401
1402 /* Update/add Content-Type header field. */
1403 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1404 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1405 found_content_type = 1;
1406 free (hf->value);
1407 hf->value = (cp = strchr (ct->c_ctline, ';'))
1408 ? concat (type_subtypename, cp, "\n", NULL)
1409 : add (name_plus_nl, NULL);
1410 }
1411 }
1412 if (! found_content_type) {
1413 add_header (ct, add (TYPE_FIELD, NULL),
1414 (cp = strchr (ct->c_ctline, ';'))
1415 ? concat (type_subtypename, cp, "\n", NULL)
1416 : add (name_plus_nl, NULL));
1417 }
1418
1419 /* Some of these might not be used, but set them anyway. */
1420 ctline = cp
1421 ? concat (type_subtypename, cp, NULL)
1422 : concat (type_subtypename, NULL);
1423 free (ct->c_ctline);
1424 ct->c_ctline = ctline;
1425 /* Leave other ctinfo members as they were. */
1426 free (ct->c_ctinfo.ci_type);
1427 ct->c_ctinfo.ci_type = add (typename, NULL);
1428 free (ct->c_ctinfo.ci_subtype);
1429 ct->c_ctinfo.ci_subtype = add (subtypename, NULL);
1430 ct->c_type = type;
1431 ct->c_subtype = subtype;
1432
1433 free (name_plus_nl);
1434 free (type_subtypename);
1435
1436 status = set_ce (ct, encoding);
1437
1438 return status;
1439 }
1440
1441
1442 static int
1443 decode_text_parts (CT ct, int encoding, int *message_mods) {
1444 int status = OK;
1445
1446 switch (ct->c_type) {
1447 case CT_TEXT:
1448 switch (ct->c_encoding) {
1449 case CE_BASE64:
1450 case CE_QUOTED: {
1451 int ct_encoding;
1452
1453 if (decode_part (ct) == OK && ct->c_cefile.ce_file) {
1454 if ((ct_encoding = content_encoding (ct)) == CE_BINARY &&
1455 encoding != CE_BINARY) {
1456 if (verbosw) {
1457 report (ct->c_partno, ct->c_file,
1458 "will not decode%s because it is binary",
1459 ct->c_partno ? ""
1460 : ct->c_ctline ? ct->c_ctline
1461 : "");
1462 }
1463 unlink (ct->c_cefile.ce_file);
1464 free (ct->c_cefile.ce_file);
1465 ct->c_cefile.ce_file = NULL;
1466 status = NOTOK;
1467 } else if (ct->c_encoding == CE_QUOTED &&
1468 ct_encoding == CE_8BIT && encoding == CE_7BIT) {
1469 if (verbosw) {
1470 report (ct->c_partno, ct->c_file,
1471 "will not decode%s because it is 8bit",
1472 ct->c_partno ? ""
1473 : ct->c_ctline ? ct->c_ctline
1474 : "");
1475 }
1476 unlink (ct->c_cefile.ce_file);
1477 free (ct->c_cefile.ce_file);
1478 ct->c_cefile.ce_file = NULL;
1479 status = NOTOK;
1480 } else {
1481 int enc;
1482 if (ct_encoding == CE_BINARY)
1483 enc = CE_BINARY;
1484 else if (ct_encoding == CE_8BIT && encoding == CE_7BIT)
1485 enc = CE_QUOTED;
1486 else
1487 enc = charset_encoding (ct);
1488 if (set_ce (ct, enc) == OK) {
1489 ++*message_mods;
1490 if (verbosw) {
1491 report (ct->c_partno, ct->c_file, "decode%s",
1492 ct->c_ctline ? ct->c_ctline : "");
1493 }
1494 } else {
1495 status = NOTOK;
1496 }
1497 }
1498 } else {
1499 status = NOTOK;
1500 }
1501 break;
1502 }
1503 default:
1504 break;
1505 }
1506 break;
1507
1508 case CT_MULTIPART: {
1509 struct multipart *m = (struct multipart *) ct->c_ctparams;
1510 struct part *part;
1511
1512 /* Should check to see if the body for this part is encoded?
1513 For now, it gets passed along as-is by InitMultiPart(). */
1514 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
1515 status = decode_text_parts (part->mp_part, encoding, message_mods);
1516 }
1517 break;
1518 }
1519
1520 case CT_MESSAGE:
1521 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1522 struct exbody *e;
1523
1524 e = (struct exbody *) ct->c_ctparams;
1525 status = decode_text_parts (e->eb_content, encoding, message_mods);
1526 }
1527 break;
1528
1529 default:
1530 break;
1531 }
1532
1533 return status;
1534 }
1535
1536
1537 /* See if the decoded content is 7bit, 8bit, or binary. It's binary
1538 if it has any NUL characters, a CR not followed by a LF, or lines
1539 greater than 998 characters in length. */
1540 static int
1541 content_encoding (CT ct) {
1542 CE ce = &ct->c_cefile;
1543 int encoding = CE_7BIT;
1544
1545 if (ce->ce_file) {
1546 char buffer[BUFSIZ];
1547 size_t inbytes;
1548
1549 if (! ce->ce_fp && (ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1550 advise (ce->ce_file, "unable to open for reading");
1551 return CE_UNKNOWN;
1552 }
1553
1554 fseeko (ce->ce_fp, 0L, SEEK_SET);
1555 while (encoding != CE_BINARY &&
1556 (inbytes = fread (buffer, 1, sizeof buffer, ce->ce_fp)) > 0) {
1557 char *cp;
1558 size_t i;
1559 size_t line_len = 0;
1560 int last_char_was_cr = 0;
1561
1562 for (i = 0, cp = buffer; i < inbytes; ++i, ++cp) {
1563 if (*cp == '\0' || ++line_len > 998 ||
1564 (*cp != '\n' && last_char_was_cr)) {
1565 encoding = CE_BINARY;
1566 break;
1567 } else if (*cp == '\n') {
1568 line_len = 0;
1569 } else if (! isascii ((unsigned char) *cp)) {
1570 encoding = CE_8BIT;
1571 }
1572
1573 last_char_was_cr = *cp == '\r' ? 1 : 0;
1574 }
1575 }
1576
1577 fclose (ce->ce_fp);
1578 ce->ce_fp = NULL;
1579 } /* else should never happen */
1580
1581 return encoding;
1582 }
1583
1584
1585 static int
1586 convert_codesets (CT ct, char *dest_codeset, int *message_mods) {
1587 int status = OK;
1588
1589 switch (ct->c_type) {
1590 case CT_TEXT:
1591 if (ct->c_subtype == TEXT_PLAIN) {
1592 status = convert_codeset (ct, dest_codeset, message_mods);
1593 }
1594 break;
1595
1596 case CT_MULTIPART: {
1597 struct multipart *m = (struct multipart *) ct->c_ctparams;
1598 struct part *part;
1599
1600 /* Should check to see if the body for this part is encoded?
1601 For now, it gets passed along as-is by InitMultiPart(). */
1602 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
1603 status =
1604 convert_codesets (part->mp_part, dest_codeset, message_mods);
1605 }
1606 break;
1607 }
1608
1609 case CT_MESSAGE:
1610 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1611 struct exbody *e;
1612
1613 e = (struct exbody *) ct->c_ctparams;
1614 status =
1615 convert_codesets (e->eb_content, dest_codeset, message_mods);
1616 }
1617 break;
1618
1619 default:
1620 break;
1621 }
1622
1623 return status;
1624 }
1625
1626
1627 static int
1628 convert_codeset (CT ct, char *dest_codeset, int *message_mods) {
1629 const char *const charset = "charset";
1630 char **src_codeset = NULL;
1631 char *default_codeset = NULL;
1632 CI ctinfo = &ct->c_ctinfo;
1633 char **ap, **vp;
1634 int status = OK;
1635
1636 for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) {
1637 if (! strcasecmp (*ap, charset)) {
1638 src_codeset = vp;
1639 break;
1640 }
1641 }
1642 /* RFC 2045, Sec. 5.2: default to us-ascii. */
1643 if (src_codeset == NULL) src_codeset = &default_codeset;
1644 if (*src_codeset == NULL) *src_codeset = "US-ASCII";
1645
1646 if (strcmp (norm_charmap (*src_codeset), norm_charmap (dest_codeset))) {
1647 #ifdef HAVE_ICONV
1648 iconv_t conv_desc = NULL;
1649 char *dest;
1650 int fd = -1;
1651 char **file = NULL;
1652 FILE **fp = NULL;
1653 long begin;
1654 long end;
1655 int opened_input_file = 0;
1656 char src_buffer[BUFSIZ];
1657 HF hf;
1658
1659 if ((conv_desc = iconv_open (dest_codeset, *src_codeset)) ==
1660 (iconv_t) -1) {
1661 advise (NULL, "Can't convert %s to %s", *src_codeset, dest_codeset);
1662 return -1;
1663 }
1664
1665 dest = add (m_mktemp2 (tmp, invo_name, &fd, NULL), NULL);
1666
1667 if (ct->c_cefile.ce_file) {
1668 file = &ct->c_cefile.ce_file;
1669 fp = &ct->c_cefile.ce_fp;
1670 begin = 0;
1671 end = -1;
1672 } else if (ct->c_file) {
1673 file = &ct->c_file;
1674 fp = &ct->c_fp;
1675 begin = ct->c_begin;
1676 end = ct->c_end;
1677 } /* else no input file: shouldn't happen */
1678
1679 if (file && *file && fp) {
1680 if (! *fp) {
1681 if ((*fp = fopen (*file, "r")) == NULL) {
1682 advise (*file, "unable to open for reading");
1683 status = NOTOK;
1684 } else {
1685 opened_input_file = 1;
1686 }
1687 }
1688 }
1689
1690 if (fp && *fp) {
1691 size_t inbytes;
1692 size_t max = end > 0 ? (size_t) (end-begin) : sizeof src_buffer;
1693
1694 fseeko (*fp, begin, SEEK_SET);
1695 while (status == OK && max > 0 &&
1696 (inbytes = fread (src_buffer, 1, max, *fp)) > 0) {
1697 char dest_buffer[BUFSIZ];
1698 char *ib = src_buffer, *ob = dest_buffer;
1699 size_t outbytes = sizeof dest_buffer;
1700 size_t outbytes_before = outbytes;
1701
1702 if (end > 0) max -= inbytes;
1703
1704 if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) ==
1705 (size_t) -1) {
1706 status = NOTOK;
1707 break;
1708 } else {
1709 write (fd, dest_buffer, outbytes_before - outbytes);
1710 }
1711 }
1712
1713 if (opened_input_file) {
1714 fclose (*fp);
1715 *fp = NULL;
1716 }
1717 }
1718
1719 iconv_close (conv_desc);
1720 close (fd);
1721
1722 if (status == OK) {
1723 /* Replace the decoded file with the converted one. */
1724 if (ct->c_cefile.ce_file) {
1725 if (ct->c_cefile.ce_unlink) {
1726 unlink (ct->c_cefile.ce_file);
1727 }
1728 free (ct->c_cefile.ce_file);
1729 }
1730 ct->c_cefile.ce_file = dest;
1731 ct->c_cefile.ce_unlink = 1;
1732
1733 ++*message_mods;
1734 if (verbosw) {
1735 report (ct->c_partno, ct->c_file, "convert %s to %s",
1736 *src_codeset, dest_codeset);
1737 }
1738
1739 /* Update ci_attrs. */
1740 *src_codeset = dest_codeset;
1741
1742 /* Update ct->c_ctline. */
1743 if (ct->c_ctline) {
1744 char *ctline =
1745 update_attr (ct->c_ctline, "charset=", dest_codeset);
1746
1747 free (ct->c_ctline);
1748 ct->c_ctline = ctline;
1749 } /* else no CT line, which is odd */
1750
1751 /* Update Content-Type header field. */
1752 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1753 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1754 char *ctline_less_newline =
1755 update_attr (hf->value, "charset=", dest_codeset);
1756 char *ctline = concat (ctline_less_newline, "\n", NULL);
1757 free (ctline_less_newline);
1758
1759 free (hf->value);
1760 hf->value = ctline;
1761 break;
1762 }
1763 }
1764 } else {
1765 unlink (dest);
1766 }
1767 #else /* ! HAVE_ICONV */
1768 NMH_UNUSED (message_mods);
1769
1770 advise (NULL, "Can't convert %s to %s without iconv", *src_codeset,
1771 dest_codeset);
1772 status = NOTOK;
1773 #endif /* ! HAVE_ICONV */
1774 }
1775
1776 return status;
1777 }
1778
1779
1780 static int
1781 write_content (CT ct, char *input_filename, char *outfile, int modify_inplace,
1782 int message_mods) {
1783 int status = OK;
1784
1785 if (modify_inplace) {
1786 if (message_mods > 0) {
1787 if ((status = output_message (ct, outfile)) == OK) {
1788 char *infile = input_filename
1789 ? add (input_filename, NULL)
1790 : add (ct->c_file ? ct->c_file : "-", NULL);
1791
1792 if (remove_file (infile) == OK) {
1793 if (rename (outfile, infile)) {
1794 /* Rename didn't work, possibly because of an
1795 attempt to rename across filesystems. Try
1796 brute force copy. */
1797 int old = open (outfile, O_RDONLY);
1798 int new =
1799 open (infile, O_WRONLY | O_CREAT, m_gmprot ());
1800 int i = -1;
1801
1802 if (old != -1 && new != -1) {
1803 char buffer[BUFSIZ];
1804
1805 while ((i = read (old, buffer, sizeof buffer)) >
1806 0) {
1807 if (write (new, buffer, i) != i) {
1808 i = -1;
1809 break;
1810 }
1811 }
1812 }
1813 if (new != -1) close (new);
1814 if (old != -1) close (old);
1815 unlink (outfile);
1816
1817 if (i < 0) {
1818 /* The -file argument processing used path() to
1819 expand filename to absolute path. */
1820 int file = ct->c_file && ct->c_file[0] == '/';
1821
1822 admonish (NULL, "unable to rename %s %s to %s",
1823 file ? "file" : "message", outfile,
1824 infile);
1825 status = NOTOK;
1826 }
1827 }
1828 } else {
1829 admonish (NULL, "unable to remove input file %s, "
1830 "not modifying it", infile);
1831 unlink (outfile);
1832 status = NOTOK;
1833 }
1834
1835 free (infile);
1836 } else {
1837 status = NOTOK;
1838 }
1839 } else {
1840 /* No modifications and didn't need the tmp outfile. */
1841 unlink (outfile);
1842 }
1843 } else {
1844 /* Output is going to some file. Produce it whether or not
1845 there were modifications. */
1846 status = output_message (ct, outfile);
1847 }
1848
1849 flush_errors ();
1850 return status;
1851 }
1852
1853
1854 /*
1855 * If "rmmproc" is defined, call that to remove the file. Otherwise,
1856 * use the standard MH backup file.
1857 */
1858 static int
1859 remove_file (char *file) {
1860 if (rmmproc) {
1861 char *rmm_command = concat (rmmproc, " ", file, NULL);
1862 int status = system (rmm_command);
1863
1864 free (rmm_command);
1865 return WIFEXITED (status) ? WEXITSTATUS (status) : NOTOK;
1866 } else {
1867 /* This is OK for a non-message file, it still uses the
1868 BACKUP_PREFIX form. The backup file will be in the same
1869 directory as file. */
1870 return rename (file, m_backup (file));
1871 }
1872 }
1873
1874
1875 static void
1876 report (char *partno, char *filename, char *message, ...) {
1877 va_list args;
1878 char *fmt;
1879
1880 if (verbosw) {
1881 va_start (args, message);
1882 fmt = concat (filename, partno ? " part " : ", ",
1883 partno ? partno : "", partno ? ", " : "", message, NULL);
1884
1885 advertise (NULL, NULL, fmt, args);
1886
1887 free (fmt);
1888 va_end (args);
1889 }
1890 }
1891
1892
1893 static char *
1894 upcase (char *str) {
1895 char *up = cpytrim (str);
1896 char *cp;
1897
1898 for (cp = up; *cp; ++cp) *cp = toupper ((unsigned char) *cp);
1899
1900 return up;
1901 }
1902
1903
1904 static void
1905 pipeser (int i)
1906 {
1907 if (i == SIGQUIT) {
1908 fflush (stdout);
1909 fprintf (stderr, "\n");
1910 fflush (stderr);
1911 }
1912
1913 done (1);
1914 /* NOTREACHED */
1915 }