]> diplodocus.org Git - nmh/blob - uip/mhfixmsg.c
f410ecb4ff8becf8d767d338bbb6aa8b24e29d24
[nmh] / uip / mhfixmsg.c
1 /* mhfixmsg.c -- rewrite a message with various transformations
2 *
3 * This code is Copyright (c) 2002 and 2013, by the authors of nmh.
4 * See the COPYRIGHT file in the root directory of the nmh
5 * distribution for complete copyright information.
6 */
7
8 #include "h/mh.h"
9 #include "sbr/m_name.h"
10 #include "sbr/m_gmprot.h"
11 #include "sbr/m_getfld.h"
12 #include "sbr/getarguments.h"
13 #include "sbr/concat.h"
14 #include "sbr/seq_setprev.h"
15 #include "sbr/seq_setcur.h"
16 #include "sbr/seq_save.h"
17 #include "sbr/smatch.h"
18 #include "sbr/fmt_rfc2047.h"
19 #include "sbr/cpydata.h"
20 #include "sbr/trimcpy.h"
21 #include "sbr/m_convert.h"
22 #include "sbr/m_backup.h"
23 #include "sbr/getfolder.h"
24 #include "sbr/folder_read.h"
25 #include "sbr/context_save.h"
26 #include "sbr/context_replace.h"
27 #include "sbr/context_find.h"
28 #include "sbr/readconfig.h"
29 #include "sbr/ambigsw.h"
30 #include "sbr/path.h"
31 #include "sbr/print_version.h"
32 #include "sbr/print_help.h"
33 #include "sbr/error.h"
34 #include "h/fmt_scan.h"
35 #include "h/mime.h"
36 #include "h/mhparse.h"
37 #include "h/done.h"
38 #include "h/utils.h"
39 #include "h/signals.h"
40 #include "sbr/m_maildir.h"
41 #include "sbr/m_mktemp.h"
42 #include "sbr/mime_type.h"
43 #include "mhmisc.h"
44 #include "mhfree.h"
45 #include "mhoutsbr.h"
46 #include "mhshowsbr.h"
47 #include <fcntl.h>
48
49 #define MHFIXMSG_SWITCHES \
50 X("decodetext 8bit|7bit|binary", 0, DECODETEXTSW) \
51 X("nodecodetext", 0, NDECODETEXTSW) \
52 X("decodetypes", 0, DECODETYPESW) \
53 X("decodeheaderfieldbodies utf-8", 0, DECODEHEADERFIELDBODIESSW) \
54 X("nodecodeheaderfieldbodies", 0, NDECODEHEADERFIELDBODIESSW) \
55 X("crlflinebreaks", 0, CRLFLINEBREAKSSW) \
56 X("nocrlflinebreaks", 0, NCRLFLINEBREAKSSW) \
57 X("textcharset", 0, TEXTCHARSETSW) \
58 X("notextcharset", 0, NTEXTCHARSETSW) \
59 X("reformat", 0, REFORMATSW) \
60 X("noreformat", 0, NREFORMATSW) \
61 X("replacetextplain", 0, REPLACETEXTPLAINSW) \
62 X("noreplacetextplain", 0, NREPLACETEXTPLAINSW) \
63 X("fixboundary", 0, FIXBOUNDARYSW) \
64 X("nofixboundary", 0, NFIXBOUNDARYSW) \
65 X("fixcte", 0, FIXCOMPOSITECTESW) \
66 X("nofixcte", 0, NFIXCOMPOSITECTESW) \
67 X("checkbase64", 0, CHECKBASE64SW) \
68 X("nocheckbase64", 0, NCHECKBASE64SW) \
69 X("fixtype mimetype", 0, FIXTYPESW) \
70 X("file file", 0, FILESW) \
71 X("outfile file", 0, OUTFILESW) \
72 X("rmmproc program", 0, RPROCSW) \
73 X("normmproc", 0, NRPRCSW) \
74 X("changecur", 0, CHGSW) \
75 X("nochangecur", 0, NCHGSW) \
76 X("verbose", 0, VERBSW) \
77 X("noverbose", 0, NVERBSW) \
78 X("version", 0, VERSIONSW) \
79 X("help", 0, HELPSW) \
80
81 #define X(sw, minchars, id) id,
82 DEFINE_SWITCH_ENUM(MHFIXMSG);
83 #undef X
84
85 #define X(sw, minchars, id) { sw, minchars, id },
86 DEFINE_SWITCH_ARRAY(MHFIXMSG, switches);
87 #undef X
88
89
90 int verbosw;
91 int debugsw; /* Needed by mhparse.c. */
92
93 #define quitser pipeser
94
95 /*
96 * static prototypes
97 */
98 typedef struct {
99 int fixboundary;
100 int fixcompositecte;
101 svector_t fixtypes;
102 int reformat;
103 int replacetextplain;
104 int decodetext;
105 char *decodetypes;
106 char *decodeheaderfieldbodies; /* Either NULL or "utf-8". */
107 /* Whether to use CRLF linebreaks, per RFC 2046 Sec. 4.1.1, par.1. */
108 int lf_line_endings;
109 char *textcharset;
110 bool checkbase64;
111 } fix_transformations;
112
113 static int mhfixmsgsbr (CT *, char *, const fix_transformations *,
114 FILE **, char *, FILE **);
115 static int fix_boundary (CT *, int *);
116 static int copy_input_to_output (const char *, FILE *, const char *, FILE *);
117 static int get_multipart_boundary (CT, char **);
118 static int replace_boundary (CT, char *, char *);
119 static int fix_types (CT, svector_t, int *);
120 static char *replace_substring (char **, const char *, const char *);
121 static char *remove_parameter (char *, const char *);
122 static int fix_composite_cte (CT, int *);
123 static int set_ce (CT, int);
124 static int ensure_text_plain (CT *, CT, int *, int);
125 static int find_textplain_sibling (CT, int, int *);
126 static int insert_new_text_plain_part (CT, int, CT);
127 static CT build_text_plain_part (CT);
128 static int insert_into_new_mp_alt (CT *, int *);
129 static int insert_into_new_mp_mixed (CT *, const char *, int *);
130 static CT divide_part (CT);
131 static void copy_ctinfo (CI, CI);
132 static int decode_part (CT);
133 static size_t get_valid_base64 (CT, char **);
134 static size_t find_invalid_base64_pos (const char *);
135 static int check_base64_encoding (CT *);
136 static int reformat_part (CT, char *, char *, char *, int);
137 static CT build_multipart (CT, CT, int, int);
138 static int boundary_in_content (FILE **, char *, const char *);
139 static void transfer_noncontent_headers (CT, CT);
140 static int set_ct_type (CT, int type, int subtype, int encoding);
141 static int decode_text_parts (CT, int, const char *, int *);
142 static int should_decode(const char *, const char *, const char *);
143 static int content_encoding (CT, const char **);
144 static int strip_crs (CT, int *);
145 static void update_cte (CT);
146 static int least_restrictive_encoding (CT) PURE;
147 static int less_restrictive (int, int);
148 static int convert_charsets (CT, char *, int *);
149 static int fix_always (CT *, const fix_transformations *, int *);
150 static int decode_header_field_bodies (CT, int *);
151 static int fix_filename_param (char *, char *, PM *, PM *);
152 static int fix_filename_encoding (CT);
153 static int write_content (CT, const char *, char *, FILE *, int, int);
154 static void set_text_ctparams(CT, char *, int);
155 static int remove_file (const char *);
156 static void report (char *, char *, char *, char *, ...)
157 CHECK_PRINTF(4, 5);
158 static void pipeser (int);
159
160
161 int
162 main (int argc, char **argv)
163 {
164 int msgnum;
165 char *cp, *file = NULL, *folder = NULL;
166 char *maildir = NULL, buf[100], *outfile = NULL;
167 char **argp, **arguments;
168 struct msgs_array msgs = { 0, 0, NULL };
169 struct msgs *mp = NULL;
170 CT *ctp;
171 FILE *fp, *infp = NULL, *outfp = NULL;
172 bool using_stdin = false;
173 bool chgflag = true;
174 int status = OK;
175 fix_transformations fx;
176 fx.reformat = fx.fixcompositecte = fx.fixboundary = 1;
177 fx.fixtypes = NULL;
178 fx.replacetextplain = 0;
179 fx.decodetext = CE_8BIT;
180 fx.decodetypes = "text,application/ics"; /* Default, per man page. */
181 fx.decodeheaderfieldbodies = NULL;
182 fx.lf_line_endings = 0;
183 fx.textcharset = NULL;
184 fx.checkbase64 = true;
185
186 if (nmh_init(argv[0], true, false)) { return 1; }
187
188 arguments = getarguments (invo_name, argc, argv, 1);
189 argp = arguments;
190
191 /*
192 * Parse arguments
193 */
194 while ((cp = *argp++)) {
195 if (*cp == '-') {
196 switch (smatch (++cp, switches)) {
197 case AMBIGSW:
198 ambigsw (cp, switches);
199 done (1);
200 case UNKWNSW:
201 die("-%s unknown", cp);
202
203 case HELPSW:
204 snprintf (buf, sizeof buf, "%s [+folder] [msgs] [switches]",
205 invo_name);
206 print_help (buf, switches, 1);
207 done (0);
208 case VERSIONSW:
209 print_version(invo_name);
210 done (0);
211
212 case DECODETEXTSW:
213 if (! (cp = *argp++) || *cp == '-') {
214 die("missing argument to %s", argp[-2]);
215 }
216 if (! strcasecmp (cp, "8bit")) {
217 fx.decodetext = CE_8BIT;
218 } else if (! strcasecmp (cp, "7bit")) {
219 fx.decodetext = CE_7BIT;
220 } else if (! strcasecmp (cp, "binary")) {
221 fx.decodetext = CE_BINARY;
222 } else {
223 die("invalid argument to %s", argp[-2]);
224 }
225 continue;
226 case NDECODETEXTSW:
227 fx.decodetext = 0;
228 continue;
229 case DECODETYPESW:
230 if (! (cp = *argp++) || *cp == '-') {
231 die("missing argument to %s", argp[-2]);
232 }
233 fx.decodetypes = cp;
234 continue;
235 case DECODEHEADERFIELDBODIESSW:
236 if (! (cp = *argp++) || *cp == '-') {
237 die("missing argument to %s", argp[-2]);
238 }
239 fx.decodeheaderfieldbodies = cp;
240 if (strcasecmp (cp, "utf-8") && strcasecmp (cp, "utf8")) {
241 /* Because UTF-8 strings can't have embedded nulls. Other
242 encodings support that, too, but we won't bother to
243 enumerate them. */
244 die("-decodeheaderfieldbodies only supports utf-8");
245 }
246 continue;
247 case NDECODEHEADERFIELDBODIESSW:
248 fx.decodeheaderfieldbodies = NULL;
249 continue;
250 case CRLFLINEBREAKSSW:
251 fx.lf_line_endings = 0;
252 continue;
253 case NCRLFLINEBREAKSSW:
254 fx.lf_line_endings = 1;
255 continue;
256 case TEXTCHARSETSW:
257 if (! (cp = *argp++) || (*cp == '-' && cp[1])) {
258 die("missing argument to %s", argp[-2]);
259 }
260 fx.textcharset = cp;
261 continue;
262 case NTEXTCHARSETSW:
263 fx.textcharset = 0;
264 continue;
265 case CHECKBASE64SW:
266 fx.checkbase64 = true;
267 continue;
268 case NCHECKBASE64SW:
269 fx.checkbase64 = false;
270 continue;
271 case FIXBOUNDARYSW:
272 fx.fixboundary = 1;
273 continue;
274 case NFIXBOUNDARYSW:
275 fx.fixboundary = 0;
276 continue;
277 case FIXCOMPOSITECTESW:
278 fx.fixcompositecte = 1;
279 continue;
280 case NFIXCOMPOSITECTESW:
281 fx.fixcompositecte = 0;
282 continue;
283 case FIXTYPESW:
284 if (! (cp = *argp++) || (*cp == '-' && cp[1])) {
285 die("missing argument to %s", argp[-2]);
286 }
287 if (! strncasecmp (cp, "multipart/", 10) ||
288 ! strncasecmp (cp, "message/", 8))
289 die("-fixtype %s not allowed", cp);
290 if (! strchr (cp, '/'))
291 die("-fixtype requires type/subtype");
292 if (fx.fixtypes == NULL) { fx.fixtypes = svector_create (10); }
293 svector_push_back (fx.fixtypes, cp);
294 continue;
295 case REFORMATSW:
296 fx.reformat = 1;
297 continue;
298 case NREFORMATSW:
299 fx.reformat = 0;
300 continue;
301 case REPLACETEXTPLAINSW:
302 fx.replacetextplain = 1;
303 continue;
304 case NREPLACETEXTPLAINSW:
305 fx.replacetextplain = 0;
306 continue;
307 case FILESW:
308 if (! (cp = *argp++) || (*cp == '-' && cp[1])) {
309 die("missing argument to %s", argp[-2]);
310 }
311 file = *cp == '-' ? mh_xstrdup (cp) : path (cp, TFILE);
312 continue;
313 case OUTFILESW:
314 if (! (cp = *argp++) || (*cp == '-' && cp[1])) {
315 die("missing argument to %s", argp[-2]);
316 }
317 outfile = *cp == '-' ? mh_xstrdup (cp) : path (cp, TFILE);
318 continue;
319 case RPROCSW:
320 if (!(rmmproc = *argp++) || *rmmproc == '-') {
321 die("missing argument to %s", argp[-2]);
322 }
323 continue;
324 case NRPRCSW:
325 rmmproc = NULL;
326 continue;
327 case CHGSW:
328 chgflag = true;
329 continue;
330 case NCHGSW:
331 chgflag = false;
332 continue;
333 case VERBSW:
334 verbosw = 1;
335 continue;
336 case NVERBSW:
337 verbosw = 0;
338 continue;
339 }
340 }
341 if (*cp == '+' || *cp == '@') {
342 if (folder)
343 die("only one folder at a time!");
344 folder = pluspath (cp);
345 } else {
346 if (*cp == '/') {
347 /* Interpret a full path as a filename, not a message. */
348 file = mh_xstrdup (cp);
349 } else {
350 app_msgarg (&msgs, cp);
351 }
352 }
353 }
354
355 SIGNAL (SIGQUIT, quitser);
356 SIGNAL (SIGPIPE, pipeser);
357
358 /*
359 * Read the standard profile setup
360 */
361 if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) {
362 readconfig(NULL, fp, cp, 0);
363 fclose (fp);
364 }
365
366 suppress_bogus_mp_content_warning = skip_mp_cte_check = true;
367 suppress_extraneous_trailing_semicolon_warning = true;
368
369 if (! context_find ("path")) {
370 free (path ("./", TFOLDER));
371 }
372
373 if (file && msgs.size) {
374 die("cannot specify msg and file at same time!");
375 }
376
377 if (outfile) {
378 /* Open the outfile now, so we don't have to risk opening it
379 after running out of fds. */
380 if (strcmp (outfile, "-") == 0) {
381 outfp = stdout;
382 } else if ((outfp = fopen (outfile, "w")) == NULL) {
383 adios (outfile, "unable to open for writing");
384 }
385 }
386
387 /*
388 * check if message is coming from file
389 */
390 if (file) {
391 /* If file is stdin, create a tmp file name before parse_mime()
392 has a chance, because it might put in on a different
393 filesystem than the output file. Instead, put it in the
394 user's preferred tmp directory. */
395 CT ct;
396
397 if (! strcmp ("-", file)) {
398 int fd;
399 char *cp;
400
401 using_stdin = true;
402
403 if ((cp = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
404 die("unable to create temporary file in %s",
405 get_temp_dir());
406 } else {
407 free (file);
408 file = mh_xstrdup (cp);
409 cpydata (STDIN_FILENO, fd, "-", file);
410 }
411
412 if (close (fd)) {
413 (void) m_unlink (file);
414 die("failed to write temporary file");
415 }
416 }
417
418 cts = mh_xcalloc(2, sizeof *cts);
419 ctp = cts;
420
421 if ((ct = parse_mime (file))) {
422 set_text_ctparams(ct, fx.decodetypes, fx.lf_line_endings);
423 *ctp++ = ct;
424 } else {
425 inform("unable to parse message from file %s", file);
426 status = NOTOK;
427
428 /* If there's an outfile, pass the input message unchanged, so the
429 message won't get dropped from a pipeline. */
430 if (outfile) {
431 /* Something went wrong. Output might be expected, such as if
432 this were run as a filter. Just copy the input to the
433 output. */
434 if ((infp = fopen (file, "r")) == NULL) {
435 adios (file, "unable to open for reading");
436 }
437
438 if (copy_input_to_output (file, infp, outfile, outfp) != OK) {
439 inform("unable to copy message to %s, "
440 "it might be lost\n", outfile);
441 }
442
443 fclose (infp);
444 infp = NULL;
445 }
446 }
447 } else {
448 /*
449 * message(s) are coming from a folder
450 */
451 CT ct;
452
453 if (! msgs.size) {
454 app_msgarg(&msgs, "cur");
455 }
456 if (! folder) {
457 folder = getfolder (1);
458 }
459 maildir = mh_xstrdup(m_maildir (folder));
460
461 /* chdir so that error messages, esp. from MIME parser, just
462 refer to the message and not its path. */
463 if (chdir (maildir) == NOTOK) {
464 adios (maildir, "unable to change directory to");
465 }
466
467 /* read folder and create message structure */
468 if (! (mp = folder_read (folder, 1))) {
469 die("unable to read folder %s", folder);
470 }
471
472 /* check for empty folder */
473 if (mp->nummsg == 0) {
474 die("no messages in %s", folder);
475 }
476
477 /* parse all the message ranges/sequences and set SELECTED */
478 for (msgnum = 0; msgnum < msgs.size; msgnum++)
479 if (! m_convert (mp, msgs.msgs[msgnum])) {
480 done (1);
481 }
482 seq_setprev (mp); /* set the previous-sequence */
483
484 cts = mh_xcalloc(mp->numsel + 1, sizeof *cts);
485 ctp = cts;
486
487 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
488 if (is_selected(mp, msgnum)) {
489 char *msgnam = m_name (msgnum);
490
491 if ((ct = parse_mime (msgnam))) {
492 set_text_ctparams(ct, fx.decodetypes, fx.lf_line_endings);
493 *ctp++ = ct;
494 } else {
495 inform("unable to parse message %s", msgnam);
496 status = NOTOK;
497
498 /* If there's an outfile, pass the input message
499 unchanged, so the message won't get dropped from a
500 pipeline. */
501 if (outfile) {
502 /* Something went wrong. Output might be expected,
503 such as if this were run as a filter. Just copy
504 the input to the output. */
505 /* Can't use path() here because 1) it might have been
506 called before and it caches the pwd, and 2) we call
507 chdir() after that. */
508 char *input_filename =
509 concat (maildir, "/", msgnam, NULL);
510
511 if ((infp = fopen (input_filename, "r")) == NULL) {
512 adios (input_filename,
513 "unable to open for reading");
514 }
515
516 if (copy_input_to_output (input_filename, infp,
517 outfile, outfp) != OK) {
518 inform("unable to copy message to %s, "
519 "it might be lost\n", outfile);
520 }
521
522 fclose (infp);
523 infp = NULL;
524 free (input_filename);
525 }
526 }
527 }
528 }
529
530 if (chgflag) {
531 seq_setcur (mp, mp->hghsel); /* update current message */
532 }
533 seq_save (mp); /* synchronize sequences */
534 context_replace (pfolder, folder);/* update current folder */
535 context_save (); /* save the context file */
536 }
537
538 if (*cts) {
539 for (ctp = cts; *ctp; ++ctp) {
540 status =
541 mhfixmsgsbr (ctp, maildir, &fx, &infp, outfile, &outfp) == OK
542 ? 0
543 : 1;
544 free_content (*ctp);
545
546 if (using_stdin) {
547 (void) m_unlink (file);
548
549 if (! outfile) {
550 /* Just calling m_backup() unlinks the backup file. */
551 (void) m_backup (file);
552 }
553 }
554 }
555 } else {
556 status = 1;
557 }
558
559 free(maildir);
560 free (cts);
561
562 if (fx.fixtypes != NULL) { svector_free (fx.fixtypes); }
563 if (infp) { fclose (infp); } /* even if stdin */
564 if (outfp) { fclose (outfp); } /* even if stdout */
565 free (outfile);
566 free (file);
567 free (folder);
568 free (arguments);
569
570 done (status == OK ? 0 : 1);
571 return NOTOK;
572 }
573
574
575 /*
576 * Apply transformations to one message.
577 */
578 static int
579 mhfixmsgsbr (CT *ctp, char *maildir, const fix_transformations *fx,
580 FILE **infp, char *outfile, FILE **outfp)
581 {
582 /* Store input filename in case one of the transformations, i.e.,
583 fix_boundary(), rewrites to a tmp file. */
584 char *input_filename = maildir
585 ? concat (maildir, "/", (*ctp)->c_file, NULL)
586 : mh_xstrdup ((*ctp)->c_file);
587 bool modify_inplace = false;
588 int message_mods = 0;
589 int status = OK;
590
591 /* Though the input file won't need to be opened if everything goes
592 well, do it here just in case there's a failure, and that failure is
593 running out of file descriptors. */
594 if ((*infp = fopen (input_filename, "r")) == NULL) {
595 adios (input_filename, "unable to open for reading");
596 }
597
598 if (outfile == NULL) {
599 modify_inplace = true;
600
601 if ((*ctp)->c_file) {
602 char *tempfile;
603 /* outfp will be closed by the caller */
604 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, outfp)) ==
605 NULL) {
606 die("unable to create temporary file in %s",
607 get_temp_dir());
608 }
609 outfile = mh_xstrdup (tempfile);
610 } else {
611 die("missing both input and output filenames\n");
612 }
613 } /* else *outfp was defined by caller */
614
615 reverse_alternative_parts (*ctp);
616 status = fix_always (ctp, fx, &message_mods);
617 if (status == OK && fx->fixboundary) {
618 status = fix_boundary (ctp, &message_mods);
619 }
620 if (status == OK && fx->fixtypes != NULL) {
621 status = fix_types (*ctp, fx->fixtypes, &message_mods);
622 }
623 if (status == OK && fx->fixcompositecte) {
624 status = fix_composite_cte (*ctp, &message_mods);
625 }
626 if (status == OK && fx->reformat) {
627 status =
628 ensure_text_plain (ctp, NULL, &message_mods, fx->replacetextplain);
629 }
630 if (status == OK && fx->decodetext) {
631 status = decode_text_parts (*ctp, fx->decodetext, fx->decodetypes,
632 &message_mods);
633 update_cte (*ctp);
634 }
635 if (status == OK && fx->decodeheaderfieldbodies) {
636 status = decode_header_field_bodies(*ctp, &message_mods);
637 }
638 if (status == OK && fx->textcharset != NULL) {
639 status = convert_charsets (*ctp, fx->textcharset, &message_mods);
640 }
641
642 if (status == OK && ! (*ctp)->c_umask) {
643 /* Set the umask for the contents file. This currently
644 isn't used but just in case it is in the future. */
645 struct stat st;
646
647 if (stat ((*ctp)->c_file, &st) != NOTOK) {
648 (*ctp)->c_umask = ~(st.st_mode & 0777);
649 } else {
650 (*ctp)->c_umask = ~m_gmprot();
651 }
652 }
653
654 /*
655 * Write the content to a file
656 */
657 if (status == OK) {
658 status = write_content (*ctp, input_filename, outfile, *outfp,
659 modify_inplace, message_mods);
660 } else if (! modify_inplace) {
661 /* Something went wrong. Output might be expected, such
662 as if this were run as a filter. Just copy the input
663 to the output. */
664 if (copy_input_to_output (input_filename, *infp, outfile,
665 *outfp) != OK) {
666 inform("unable to copy message to %s, it might be lost\n",
667 outfile);
668 }
669 }
670
671 if (modify_inplace) {
672 if (status != OK) { (void) m_unlink (outfile); }
673 free (outfile);
674 outfile = NULL;
675 }
676
677 fclose (*infp);
678 *infp = NULL;
679 free (input_filename);
680
681 return status;
682 }
683
684
685 /*
686 * Copy input message to output. Assumes not modifying in place, so this
687 * might be running as part of a pipeline.
688 */
689 static int
690 copy_input_to_output (const char *input_filename, FILE *infp,
691 const char *output_filename, FILE *outfp)
692 {
693 int in = fileno (infp);
694 int out = fileno (outfp);
695 int status = OK;
696
697 if (in != -1 && out != -1) {
698 cpydata (in, out, input_filename, output_filename);
699 } else {
700 status = NOTOK;
701 }
702
703 return status;
704 }
705
706
707 /*
708 * Fix mismatched outer level boundary.
709 */
710 static int
711 fix_boundary (CT *ct, int *message_mods)
712 {
713 struct multipart *mp;
714 int status = OK;
715
716 if (ct && (*ct)->c_type == CT_MULTIPART && bogus_mp_content) {
717 mp = (struct multipart *) (*ct)->c_ctparams;
718
719 /*
720 * 1) Get boundary at end of part.
721 * 2) Get boundary at beginning of part and compare to the end-of-part
722 * boundary.
723 * 3) Write out contents of ct to tmp file, replacing boundary in
724 * header with boundary from part. Set c_unlink to 1.
725 * 4) Free ct.
726 * 5) Call parse_mime() on the tmp file, replacing ct.
727 */
728
729 if (mp && mp->mp_start) {
730 char *part_boundary;
731
732 if (get_multipart_boundary (*ct, &part_boundary) == OK) {
733 char *fixed;
734
735 if ((fixed = m_mktemp2 (NULL, invo_name, NULL, &(*ct)->c_fp))) {
736 if (replace_boundary (*ct, fixed, part_boundary) == OK) {
737 char *filename = mh_xstrdup ((*ct)->c_file);
738 CT fixed_ct;
739
740 free_content (*ct);
741 if ((fixed_ct = parse_mime (fixed))) {
742 *ct = fixed_ct;
743 (*ct)->c_unlink = 1;
744
745 ++*message_mods;
746 if (verbosw) {
747 report (NULL, NULL, filename,
748 "fix multipart boundary");
749 }
750 } else {
751 *ct = NULL;
752 inform("unable to parse fixed part");
753 status = NOTOK;
754 }
755 free (filename);
756 } else {
757 inform("unable to replace broken boundary");
758 status = NOTOK;
759 }
760 } else {
761 inform("unable to create temporary file in %s",
762 get_temp_dir());
763 status = NOTOK;
764 }
765
766 free (part_boundary);
767 } else {
768 /* Couldn't fix the boundary. Report failure so that mhfixmsg
769 doesn't modify the message. */
770 status = NOTOK;
771 }
772 } else {
773 /* No multipart struct, even though the content type is
774 CT_MULTIPART. Report failure so that mhfixmsg doesn't modify
775 the message. */
776 status = NOTOK;
777 }
778 }
779
780 return status;
781 }
782
783
784 /*
785 * Find boundary at end of multipart.
786 */
787 static int
788 get_multipart_boundary (CT ct, char **part_boundary)
789 {
790 char buffer[NMH_BUFSIZ];
791 char *end_boundary = NULL;
792 off_t begin = (off_t) ct->c_end > (off_t) (ct->c_begin + sizeof buffer)
793 ? (off_t) (ct->c_end - sizeof buffer)
794 : (off_t) ct->c_begin;
795 size_t bytes_read;
796 int status = OK;
797
798 /* This will fail if the boundary spans fread() calls. NMH_BUFSIZ should
799 be big enough, even if it's just 1024, to make that unlikely. */
800
801 /* free_content() will close ct->c_fp if bogus MP boundary is fixed. */
802 if (! ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
803 advise (ct->c_file, "unable to open for reading");
804 return NOTOK;
805 }
806
807 /* Get boundary at end of multipart. */
808 while (begin >= (off_t) ct->c_begin) {
809 fseeko (ct->c_fp, begin, SEEK_SET);
810 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
811 char *cp = rfind_str (buffer, bytes_read, "--");
812
813 if (cp) {
814 char *end;
815
816 /* Trim off trailing "--" and anything beyond. */
817 *cp-- = '\0';
818 if ((end = rfind_str (buffer, cp - buffer, "\n"))) {
819 if (strlen (end) > 3 && *end++ == '\n' &&
820 *end++ == '-' && *end++ == '-') {
821 end_boundary = mh_xstrdup (end);
822 break;
823 }
824 }
825 }
826 }
827
828 if (end_boundary || begin <= (off_t) (ct->c_begin + sizeof buffer))
829 break;
830 begin -= sizeof buffer;
831 }
832
833 /* Get boundary at beginning of multipart. */
834 if (end_boundary) {
835 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
836 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
837 if (bytes_read >= strlen (end_boundary)) {
838 char *cp = find_str (buffer, bytes_read, end_boundary);
839
840 if (cp && cp - buffer >= 2 && *--cp == '-' &&
841 *--cp == '-' && (cp > buffer && *--cp == '\n')) {
842 status = OK;
843 break;
844 }
845 } else {
846 /* The start and end boundaries didn't match, or the
847 start boundary doesn't begin with "\n--" (or "--"
848 if at the beginning of buffer). Keep trying. */
849 status = NOTOK;
850 }
851 }
852 } else {
853 status = NOTOK;
854 }
855
856 if (ct->c_fp) {
857 fclose (ct->c_fp);
858 ct->c_fp = NULL;
859 }
860
861 if (status == OK) {
862 *part_boundary = end_boundary;
863 } else {
864 *part_boundary = NULL;
865 free (end_boundary);
866 }
867
868 return status;
869 }
870
871
872 /*
873 * Open and copy ct->c_file to file, replacing the multipart boundary.
874 */
875 static int
876 replace_boundary (CT ct, char *file, char *boundary)
877 {
878 FILE *fpin, *fpout;
879 int compnum, state;
880 char buf[NMH_BUFSIZ], name[NAMESZ];
881 char *np, *vp;
882 m_getfld_state_t gstate;
883 int status = OK;
884
885 if (ct->c_file == NULL) {
886 inform("missing input filename");
887 return NOTOK;
888 }
889
890 if ((fpin = fopen (ct->c_file, "r")) == NULL) {
891 advise (ct->c_file, "unable to open for reading");
892 return NOTOK;
893 }
894
895 if ((fpout = fopen (file, "w")) == NULL) {
896 fclose (fpin);
897 advise (file, "unable to open for writing");
898 return NOTOK;
899 }
900
901 gstate = m_getfld_state_init(fpin);
902 for (compnum = 1;;) {
903 int bufsz = (int) sizeof buf;
904
905 switch (state = m_getfld2(&gstate, name, buf, &bufsz)) {
906 case FLD:
907 case FLDPLUS:
908 compnum++;
909
910 /* get copies of the buffers */
911 np = mh_xstrdup (name);
912 vp = mh_xstrdup (buf);
913
914 /* if necessary, get rest of field */
915 while (state == FLDPLUS) {
916 bufsz = sizeof buf;
917 state = m_getfld2(&gstate, name, buf, &bufsz);
918 vp = add (buf, vp); /* add to previous value */
919 }
920
921 if (strcasecmp (TYPE_FIELD, np)) {
922 fprintf (fpout, "%s:%s", np, vp);
923 } else {
924 char *new_ctline, *new_params;
925
926 replace_param(&ct->c_ctinfo.ci_first_pm,
927 &ct->c_ctinfo.ci_last_pm, "boundary",
928 boundary, 0);
929
930 new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
931 ct->c_ctinfo.ci_subtype, NULL);
932 new_params = output_params(LEN(TYPE_FIELD) +
933 strlen(new_ctline) + 1,
934 ct->c_ctinfo.ci_first_pm, NULL, 0);
935 fprintf (fpout, "%s:%s%s\n", np, new_ctline,
936 FENDNULL(new_params));
937 free(new_ctline);
938 free(new_params);
939 }
940
941 free (vp);
942 free (np);
943
944 continue;
945
946 case BODY:
947 putc('\n', fpout);
948 /* buf will have a terminating NULL, skip it. */
949 if ((int) fwrite (buf, 1, bufsz-1, fpout) < bufsz-1) {
950 advise (file, "fwrite");
951 }
952 continue;
953
954 case FILEEOF:
955 break;
956
957 case LENERR:
958 case FMTERR:
959 inform("message format error in component #%d", compnum);
960 status = NOTOK;
961 break;
962
963 default:
964 inform("getfld() returned %d", state);
965 status = NOTOK;
966 break;
967 }
968
969 break;
970 }
971
972 m_getfld_state_destroy (&gstate);
973 fclose (fpout);
974 fclose (fpin);
975
976 return status;
977 }
978
979
980 /*
981 * Fix Content-Type header to reflect the content of its part.
982 */
983 static int
984 fix_types (CT ct, svector_t fixtypes, int *message_mods)
985 {
986 int status = OK;
987
988 switch (ct->c_type) {
989 case CT_MULTIPART: {
990 struct multipart *m = (struct multipart *) ct->c_ctparams;
991 struct part *part;
992
993 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
994 status = fix_types (part->mp_part, fixtypes, message_mods);
995 }
996 break;
997 }
998
999 case CT_MESSAGE:
1000 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1001 struct exbody *e = (struct exbody *) ct->c_ctparams;
1002
1003 status = fix_types (e->eb_content, fixtypes, message_mods);
1004 }
1005 break;
1006
1007 default: {
1008 char **typep, *type;
1009
1010 if (ct->c_ctinfo.ci_type && ct->c_ctinfo.ci_subtype) {
1011 for (typep = svector_strs (fixtypes);
1012 typep && (type = *typep);
1013 ++typep) {
1014 char *type_subtype =
1015 concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
1016 NULL);
1017
1018 if (! strcasecmp (type, type_subtype) &&
1019 decode_part (ct) == OK &&
1020 ct->c_cefile.ce_file != NULL) {
1021 char *ct_type_subtype = mime_type (ct->c_cefile.ce_file);
1022 char *cp;
1023
1024 if ((cp = strchr (ct_type_subtype, ';'))) {
1025 /* Truncate to remove any parameter list from
1026 mime_type () result. */
1027 *cp = '\0';
1028 }
1029
1030 if (strcasecmp (type, ct_type_subtype)) {
1031 char *ct_type, *ct_subtype;
1032 HF hf;
1033
1034 /* The Content-Type header does not match the
1035 content, so update these struct Content
1036 fields to match:
1037 * c_type, c_subtype
1038 * c_ctinfo.ci_type, c_ctinfo.ci_subtype
1039 * c_ctline
1040 */
1041 /* Extract type and subtype from type/subtype. */
1042 ct_type = mh_xstrdup(ct_type_subtype);
1043 if ((cp = strchr (ct_type, '/'))) {
1044 *cp = '\0';
1045 ct_subtype = mh_xstrdup(++cp);
1046 } else {
1047 inform("missing / in MIME type of %s %s",
1048 ct->c_file, ct->c_partno);
1049 free (ct_type);
1050 return NOTOK;
1051 }
1052
1053 ct->c_type = ct_str_type (ct_type);
1054 ct->c_subtype = ct_str_subtype (ct->c_type, ct_subtype);
1055
1056 free (ct->c_ctinfo.ci_type);
1057 ct->c_ctinfo.ci_type = ct_type;
1058 free (ct->c_ctinfo.ci_subtype);
1059 ct->c_ctinfo.ci_subtype = ct_subtype;
1060 if (! replace_substring (&ct->c_ctline, type,
1061 ct_type_subtype)) {
1062 inform("did not find %s in %s",
1063 type, ct->c_ctline);
1064 }
1065
1066 /* Update Content-Type header field. */
1067 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1068 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1069 if (replace_substring (&hf->value, type,
1070 ct_type_subtype)) {
1071 ++*message_mods;
1072 if (verbosw) {
1073 report (NULL, ct->c_partno, ct->c_file,
1074 "change Content-Type in header "
1075 "from %s to %s",
1076 type, ct_type_subtype);
1077 }
1078 break;
1079 }
1080 inform("did not find %s in %s", type, hf->value);
1081 }
1082 }
1083 }
1084 free (ct_type_subtype);
1085 }
1086 free (type_subtype);
1087 }
1088 }
1089 }}
1090
1091 return status;
1092 }
1093
1094
1095 /*
1096 * Replace a substring, allocating space to hold the new one.
1097 */
1098 char *
1099 replace_substring (char **str, const char *old, const char *new)
1100 {
1101 char *cp;
1102
1103 if ((cp = strstr (*str, old))) {
1104 char *remainder = cp + strlen (old);
1105 char *prefix, *new_str;
1106
1107 if (cp - *str) {
1108 prefix = mh_xstrdup(*str);
1109 *(prefix + (cp - *str)) = '\0';
1110 new_str = concat (prefix, new, remainder, NULL);
1111 free (prefix);
1112 } else {
1113 new_str = concat (new, remainder, NULL);
1114 }
1115
1116 free (*str);
1117
1118 return *str = new_str;
1119 }
1120
1121 return NULL;
1122 }
1123
1124
1125 /*
1126 * Remove a name=value parameter, given just its name, from a header value.
1127 */
1128 char *
1129 remove_parameter (char *str, const char *name)
1130 {
1131 /* It looks to me, based on the BNF in RFC 2045, than there can't
1132 be whitespace between the parameter name and the "=", or
1133 between the "=" and the parameter value. */
1134 char *param_name = concat (name, "=", NULL);
1135 char *cp;
1136
1137 if ((cp = strstr (str, param_name))) {
1138 char *start, *end;
1139 size_t count = 1;
1140
1141 /* Remove any leading spaces, before the parameter name. */
1142 for (start = cp;
1143 start > str && isspace ((unsigned char) *(start-1));
1144 --start) {
1145 continue;
1146 }
1147 /* Remove a leading semicolon. */
1148 if (start > str && *(start-1) == ';') { --start; }
1149
1150 end = cp + strlen (name) + 1;
1151 if (*end == '"') {
1152 /* Skip past the quoted value, and then the final quote. */
1153 for (++end ; *end && *end != '"'; ++end) { continue; }
1154 ++end;
1155 } else {
1156 /* Skip past the value. */
1157 for (++end ; *end && ! isspace ((unsigned char) *end); ++end) {}
1158 }
1159
1160 /* Count how many characters need to be moved. Include
1161 trailing null, which is accounted for by the
1162 initialization of count to 1. */
1163 for (cp = end; *cp; ++cp) { ++count; }
1164 (void) memmove (start, end, count);
1165 }
1166
1167 free (param_name);
1168
1169 return str;
1170 }
1171
1172
1173 /*
1174 * Fix Content-Transfer-Encoding of composite,, e.g., message or multipart, part.
1175 * According to RFC 2045 Sec. 6.4, it must be 7bit, 8bit, or binary. Set it to
1176 * 8 bit.
1177 */
1178 static int
1179 fix_composite_cte (CT ct, int *message_mods)
1180 {
1181 int status = OK;
1182
1183 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) {
1184 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT &&
1185 ct->c_encoding != CE_BINARY) {
1186 HF hf;
1187
1188 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1189 char *name = hf->name;
1190 for (; isspace((unsigned char)*name); ++name) {
1191 continue;
1192 }
1193
1194 if (! strncasecmp (name, ENCODING_FIELD,
1195 LEN(ENCODING_FIELD))) {
1196 char *prefix = "Nmh-REPLACED-INVALID-";
1197 HF h;
1198
1199 NEW(h);
1200 h->name = mh_xstrdup (hf->name);
1201 h->next = hf->next;
1202 hf->next = h;
1203
1204 /* Retain old header but prefix its name. */
1205 free (hf->name);
1206 hf->name = concat (prefix, h->name, NULL);
1207
1208 ++*message_mods;
1209 if (verbosw) {
1210 char *encoding = cpytrim (hf->value);
1211 report (NULL, ct->c_partno, ct->c_file,
1212 "replace Content-Transfer-Encoding of %s "
1213 "with 8 bit", encoding);
1214 free (encoding);
1215 }
1216
1217 h->value = mh_xstrdup (" 8bit\n");
1218
1219 /* Don't need to warn for multiple C-T-E header
1220 fields, parse_mime() already does that. But
1221 if there are any, fix them all as necessary. */
1222 hf = h;
1223 }
1224 }
1225
1226 set_ce (ct, CE_8BIT);
1227 }
1228
1229 if (ct->c_type == CT_MULTIPART) {
1230 struct multipart *m;
1231 struct part *part;
1232
1233 m = (struct multipart *) ct->c_ctparams;
1234 for (part = m->mp_parts; part; part = part->mp_next) {
1235 if (fix_composite_cte (part->mp_part, message_mods) != OK) {
1236 status = NOTOK;
1237 break;
1238 }
1239 }
1240 }
1241 }
1242
1243 return status;
1244 }
1245
1246
1247 /*
1248 * Set content encoding.
1249 */
1250 static int
1251 set_ce (CT ct, int encoding)
1252 {
1253 const char *ce = ce_str (encoding);
1254 const struct str2init *ctinit = get_ce_method (ce);
1255
1256 if (ctinit) {
1257 char *cte = concat (" ", ce, "\n", NULL);
1258 bool found_cte = false;
1259 HF hf;
1260 /* Decoded contents might be in ct->c_cefile.ce_file, if the
1261 caller is decode_text_parts (). Save because we'll
1262 overwrite below. */
1263 struct cefile decoded_content_info = ct->c_cefile;
1264
1265 ct->c_encoding = encoding;
1266
1267 ct->c_ctinitfnx = ctinit->si_init;
1268 /* This will assign ct->c_cefile with an all-0 struct, which
1269 is what we want. */
1270 (*ctinit->si_init) (ct);
1271 /* After returning, the caller should set
1272 ct->c_cefile.ce_file to the name of the file containing
1273 the contents. */
1274
1275 if (ct->c_ceclosefnx) {
1276 (*ct->c_ceclosefnx) (ct);
1277 }
1278
1279 /* Restore the cefile. */
1280 ct->c_cefile = decoded_content_info;
1281
1282 /* Update/add Content-Transfer-Encoding header field. */
1283 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1284 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
1285 found_cte = true;
1286 free (hf->value);
1287 hf->value = cte;
1288 }
1289 }
1290 if (! found_cte) {
1291 add_header (ct, mh_xstrdup (ENCODING_FIELD), cte);
1292 }
1293
1294 /* Update c_celine. It's used only by mhlist -debug. */
1295 free (ct->c_celine);
1296 ct->c_celine = mh_xstrdup (cte);
1297
1298 return OK;
1299 }
1300
1301 return NOTOK;
1302 }
1303
1304
1305 /*
1306 * Make sure each text part has a corresponding text/plain part.
1307 */
1308 static int
1309 ensure_text_plain (CT *ct, CT parent, int *message_mods, int replacetextplain)
1310 {
1311 int status = OK;
1312
1313 switch ((*ct)->c_type) {
1314 case CT_TEXT: {
1315 /* Nothing to do for text/plain. */
1316 if ((*ct)->c_subtype == TEXT_PLAIN) { return OK; }
1317
1318 if (parent && parent->c_type == CT_MULTIPART &&
1319 parent->c_subtype == MULTI_ALTERNATE) {
1320 int new_subpart_number = 1;
1321 int has_text_plain =
1322 find_textplain_sibling (parent, replacetextplain,
1323 &new_subpart_number);
1324
1325 if (! has_text_plain) {
1326 /* Parent is a multipart/alternative. Insert a new
1327 text/plain subpart. */
1328 const int inserted =
1329 insert_new_text_plain_part (*ct, new_subpart_number,
1330 parent);
1331 if (inserted) {
1332 ++*message_mods;
1333 if (verbosw) {
1334 report (NULL, parent->c_partno, parent->c_file,
1335 "insert text/plain part");
1336 }
1337 } else {
1338 status = NOTOK;
1339 }
1340 }
1341 } else if (parent && parent->c_type == CT_MULTIPART &&
1342 parent->c_subtype == MULTI_RELATED) {
1343 char *type_subtype =
1344 concat ((*ct)->c_ctinfo.ci_type, "/",
1345 (*ct)->c_ctinfo.ci_subtype, NULL);
1346 const char *parent_type =
1347 get_param (parent->c_ctinfo.ci_first_pm, "type", '?', 1);
1348 int new_subpart_number = 1;
1349 int has_text_plain = 0;
1350
1351 /* Have to do string comparison on the subtype because we
1352 don't enumerate all of them in c_subtype values.
1353 parent_type will be NULL if the multipart/related part
1354 doesn't have a type parameter. The type parameter must
1355 be specified according to RFC 2387 Sec. 3.1 but not all
1356 messages comply. */
1357 if (parent_type && strcasecmp (type_subtype, parent_type) == 0) {
1358 /* The type of this part matches the root type of the
1359 parent multipart/related. Look to see if there's
1360 text/plain sibling. */
1361 has_text_plain =
1362 find_textplain_sibling (parent, replacetextplain,
1363 &new_subpart_number);
1364 }
1365
1366 free (type_subtype);
1367
1368 if (! has_text_plain) {
1369 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1370 struct part *part;
1371 int siblings = 0;
1372
1373 for (part = mp->mp_parts; part; part = part->mp_next) {
1374 if (*ct != part->mp_part) {
1375 ++siblings;
1376 }
1377 }
1378
1379 if (siblings) {
1380 /* Parent is a multipart/related. Insert a new
1381 text/plain subpart in a new multipart/alternative. */
1382 if (insert_into_new_mp_alt (ct, message_mods)) {
1383 /* Not an error if text/plain couldn't be added. */
1384 }
1385 } else {
1386 /* There are no siblings, so insert a new text/plain
1387 subpart, and change the parent type from
1388 multipart/related to multipart/alternative. */
1389 const int inserted =
1390 insert_new_text_plain_part (*ct, new_subpart_number,
1391 parent);
1392
1393 if (inserted) {
1394 HF hf;
1395
1396 parent->c_subtype = MULTI_ALTERNATE;
1397 free (parent->c_ctinfo.ci_subtype);
1398 parent->c_ctinfo.ci_subtype = mh_xstrdup("alternative");
1399 if (! replace_substring (&parent->c_ctline, "/related",
1400 "/alternative")) {
1401 inform("did not find multipart/related in %s",
1402 parent->c_ctline);
1403 }
1404
1405 /* Update Content-Type header field. */
1406 for (hf = parent->c_first_hf; hf; hf = hf->next) {
1407 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1408 if (replace_substring (&hf->value, "/related",
1409 "/alternative")) {
1410 ++*message_mods;
1411 if (verbosw) {
1412 report (NULL, parent->c_partno,
1413 parent->c_file,
1414 "insert text/plain part");
1415 }
1416
1417 /* Remove, e.g., type="text/html" from
1418 multipart/alternative. */
1419 remove_parameter (hf->value, "type");
1420 break;
1421 }
1422 inform("did not find multipart/"
1423 "related in header %s", hf->value);
1424 }
1425 }
1426 } else {
1427 /* Not an error if text/plain couldn't be inserted. */
1428 }
1429 }
1430 }
1431 } else {
1432 if (insert_into_new_mp_alt (ct, message_mods)) {
1433 status = NOTOK;
1434 }
1435 }
1436 break;
1437 }
1438
1439 case CT_MULTIPART: {
1440 struct multipart *mp = (struct multipart *) (*ct)->c_ctparams;
1441 struct part *part;
1442
1443 for (part = mp->mp_parts; status == OK && part; part = part->mp_next) {
1444 if ((*ct)->c_type == CT_MULTIPART) {
1445 status = ensure_text_plain (&part->mp_part, *ct, message_mods,
1446 replacetextplain);
1447 }
1448 }
1449 break;
1450 }
1451
1452 case CT_MESSAGE:
1453 if ((*ct)->c_subtype == MESSAGE_EXTERNAL) {
1454 struct exbody *e = (struct exbody *) (*ct)->c_ctparams;
1455
1456 status = ensure_text_plain (&e->eb_content, *ct, message_mods,
1457 replacetextplain);
1458 }
1459 break;
1460 }
1461
1462 return status;
1463 }
1464
1465
1466 /*
1467 * See if there is a sibling text/plain, and return its subpart number.
1468 */
1469 static int
1470 find_textplain_sibling (CT parent, int replacetextplain,
1471 int *new_subpart_number)
1472 {
1473 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1474 struct part *part, *prev;
1475 bool has_text_plain = false;
1476
1477 for (prev = part = mp->mp_parts; part; part = part->mp_next) {
1478 ++*new_subpart_number;
1479 if (part->mp_part->c_type == CT_TEXT &&
1480 part->mp_part->c_subtype == TEXT_PLAIN) {
1481 if (replacetextplain) {
1482 struct part *old_part;
1483 if (part == mp->mp_parts) {
1484 old_part = mp->mp_parts;
1485 mp->mp_parts = part->mp_next;
1486 } else {
1487 old_part = prev->mp_next;
1488 prev->mp_next = part->mp_next;
1489 }
1490 if (verbosw) {
1491 report (NULL, parent->c_partno, parent->c_file,
1492 "remove text/plain part %s",
1493 old_part->mp_part->c_partno);
1494 }
1495 free_content (old_part->mp_part);
1496 free (old_part);
1497 } else {
1498 has_text_plain = true;
1499 }
1500 break;
1501 }
1502 prev = part;
1503 }
1504
1505 return has_text_plain;
1506 }
1507
1508
1509 /*
1510 * Insert a new text/plain part in a multipart part.
1511 */
1512 static int
1513 insert_new_text_plain_part (CT ct, int new_subpart_number, CT parent)
1514 {
1515 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1516 struct part *new_part;
1517
1518 NEW(new_part);
1519 if ((new_part->mp_part = build_text_plain_part (ct))) {
1520 char buffer[16];
1521 snprintf (buffer, sizeof buffer, "%d", new_subpart_number);
1522
1523 new_part->mp_next = mp->mp_parts;
1524 mp->mp_parts = new_part;
1525 new_part->mp_part->c_partno =
1526 concat (parent->c_partno ? parent->c_partno : "1", ".",
1527 buffer, NULL);
1528
1529 return 1;
1530 }
1531
1532 free_content (new_part->mp_part);
1533 free (new_part);
1534
1535 return 0;
1536 }
1537
1538
1539 /*
1540 * Create a text/plain part to go along with non-plain sibling part.
1541 */
1542 static CT
1543 build_text_plain_part (CT encoded_part)
1544 {
1545 CT tp_part = divide_part (encoded_part);
1546 char *tmp_plain_file = NULL;
1547
1548 if (decode_part (tp_part) == OK) {
1549 /* Now, tp_part->c_cefile.ce_file is the name of the tmp file that
1550 contains the decoded contents. And the decoding function, such
1551 as openQuoted, will have set ...->ce_unlink to 1 so that it will
1552 be unlinked by free_content (). */
1553 char *tempfile;
1554
1555 /* This m_mktemp2() call closes the temp file. */
1556 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
1557 inform("unable to create temporary file in %s",
1558 get_temp_dir());
1559 } else {
1560 tmp_plain_file = mh_xstrdup (tempfile);
1561 if (reformat_part (tp_part, tmp_plain_file,
1562 tp_part->c_ctinfo.ci_type,
1563 tp_part->c_ctinfo.ci_subtype,
1564 tp_part->c_type) == OK) {
1565 return tp_part;
1566 }
1567 }
1568 }
1569
1570 free_content (tp_part);
1571 if (tmp_plain_file) { (void) m_unlink (tmp_plain_file); }
1572 free (tmp_plain_file);
1573
1574 return NULL;
1575 }
1576
1577
1578 /*
1579 * Slip new text/plain part into a new multipart/alternative.
1580 */
1581 static int
1582 insert_into_new_mp_alt (CT *ct, int *message_mods)
1583 {
1584 /* The following will call decode_part(). */
1585 CT tp_part = build_text_plain_part (*ct);
1586 int status = OK;
1587
1588 if (tp_part) {
1589 CT mp_alt = build_multipart (*ct, tp_part, CT_MULTIPART,
1590 MULTI_ALTERNATE);
1591 if (mp_alt) {
1592 struct multipart *mp = (struct multipart *) mp_alt->c_ctparams;
1593
1594 if (mp && mp->mp_parts) {
1595 mp->mp_parts->mp_part = tp_part;
1596 /* Make the new multipart/alternative the parent. */
1597 *ct = mp_alt;
1598
1599 ++*message_mods;
1600 if (verbosw) {
1601 report (NULL, (*ct)->c_partno, (*ct)->c_file,
1602 "insert text/plain part");
1603 }
1604 } else {
1605 free_content (tp_part);
1606 free_content (mp_alt);
1607 status = NOTOK;
1608 }
1609 } else {
1610 status = NOTOK;
1611 }
1612 } else {
1613 /* Not an error if text/plain couldn't be built. */
1614 }
1615
1616 return status;
1617 }
1618
1619
1620 /*
1621 * Slip new text/plain part into a new multipart/mixed.
1622 */
1623 static int
1624 insert_into_new_mp_mixed (CT *ct, const char *content, int *message_mods)
1625 {
1626 CT main_part = divide_part (*ct);
1627 const char *reason = NULL;
1628 const int encoding = content_encoding (main_part, &reason);
1629 int status = OK;
1630
1631 if (set_ct_type(main_part, (*ct)->c_type, (*ct)->c_subtype,
1632 main_part->c_encoding) != OK) {
1633 inform("failed to set Content-Type of main part");
1634 return NOTOK;
1635 }
1636 if (set_ct_type(*ct, (*ct)->c_type, (*ct)->c_subtype, encoding) != OK) {
1637 inform("failed to set Content-Type of new part");
1638 return NOTOK;
1639 }
1640
1641 if (main_part) {
1642 /* Load remainder into the new part. */
1643 CE cefile = &(*ct)->c_cefile;
1644 CT mp_alt;
1645
1646 cefile->ce_file =
1647 mh_xstrdup(m_mktemp2 (NULL, invo_name, NULL, &cefile->ce_fp));
1648 if (cefile->ce_file == NULL) {
1649 die("unable to create temporary file in %s", get_temp_dir());
1650 }
1651 cefile->ce_unlink = 1;
1652 fprintf (cefile->ce_fp, "%s", content);
1653
1654 /* Put both parts into a new multipart. */
1655 mp_alt = build_multipart (*ct, main_part, CT_MULTIPART, MULTI_MIXED);
1656 if (mp_alt) {
1657 struct multipart *mp = (struct multipart *) mp_alt->c_ctparams;
1658
1659 /* So fix_composite_cte doesn't try to overwrite the encoding. If
1660 the content needs to be decoded, c_encoding will be properly
1661 set. */
1662 mp_alt->c_encoding = encoding;
1663
1664 if (mp && mp->mp_parts) {
1665 mp->mp_parts->mp_part = main_part;
1666 /* Make the new multipart/alternative the parent. */
1667 *ct = mp_alt;
1668
1669 ++*message_mods;
1670 if (verbosw) {
1671 report (NULL, (*ct)->c_partno, (*ct)->c_file,
1672 "insert text/plain part");
1673 }
1674 } else {
1675 free_content (main_part);
1676 free_content (mp_alt);
1677 status = NOTOK;
1678 }
1679 } else {
1680 inform("failed to build multipart/alternate");
1681 status = NOTOK;
1682 }
1683 } else {
1684 /* Should never happen. */
1685 inform("failed to insert new text part into multipart/related");
1686 status = NOTOK;
1687 }
1688
1689 return status;
1690 }
1691
1692
1693 /*
1694 * Clone a MIME part.
1695 */
1696 static CT
1697 divide_part (CT ct)
1698 {
1699 CT new_part;
1700
1701 NEW0(new_part);
1702 /* Just copy over what is needed for decoding. c_vrsn and
1703 c_celine aren't necessary. */
1704 new_part->c_file = mh_xstrdup (ct->c_file);
1705 new_part->c_begin = ct->c_begin;
1706 new_part->c_end = ct->c_end;
1707 copy_ctinfo (&new_part->c_ctinfo, &ct->c_ctinfo);
1708 new_part->c_type = ct->c_type;
1709 new_part->c_cefile = ct->c_cefile;
1710 new_part->c_encoding = ct->c_encoding;
1711 new_part->c_ctinitfnx = ct->c_ctinitfnx;
1712 new_part->c_ceopenfnx = ct->c_ceopenfnx;
1713 new_part->c_ceclosefnx = ct->c_ceclosefnx;
1714 new_part->c_cesizefnx = ct->c_cesizefnx;
1715
1716 /* c_ctline is used by reformat__part(), so it can preserve
1717 anything after the type/subtype. */
1718 new_part->c_ctline = mh_xstrdup (ct->c_ctline);
1719
1720 return new_part;
1721 }
1722
1723
1724 /*
1725 * Copy the content info from one part to another.
1726 */
1727 static void
1728 copy_ctinfo (CI dest, CI src)
1729 {
1730 PM s_pm, d_pm;
1731
1732 dest->ci_type = src->ci_type ? mh_xstrdup (src->ci_type) : NULL;
1733 dest->ci_subtype = src->ci_subtype ? mh_xstrdup (src->ci_subtype) : NULL;
1734
1735 for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) {
1736 d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name,
1737 s_pm->pm_value, 0);
1738 if (s_pm->pm_charset) {
1739 d_pm->pm_charset = mh_xstrdup(s_pm->pm_charset);
1740 }
1741 if (s_pm->pm_lang) {
1742 d_pm->pm_lang = mh_xstrdup(s_pm->pm_lang);
1743 }
1744 }
1745
1746 dest->ci_comment = src->ci_comment ? mh_xstrdup (src->ci_comment) : NULL;
1747 dest->ci_magic = src->ci_magic ? mh_xstrdup (src->ci_magic) : NULL;
1748 }
1749
1750
1751 /*
1752 * Decode content.
1753 */
1754 static int
1755 decode_part (CT ct)
1756 {
1757 char *tempfile, *tmp_decoded;
1758 FILE *file;
1759 int status;
1760
1761 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, &file)) == NULL) {
1762 die("unable to create temporary file in %s", get_temp_dir());
1763 }
1764 tmp_decoded = mh_xstrdup (tempfile);
1765 /* The following call will load ct->c_cefile.ce_file with the tmp
1766 filename of the decoded content. tmp_decoded will contain the
1767 encoded output, get rid of that. */
1768 status = output_message_fp (ct, file, tmp_decoded);
1769 (void) m_unlink (tmp_decoded);
1770 free (tmp_decoded);
1771 if (fclose (file)) {
1772 inform("unable to close temporary file %s, continuing...", tempfile);
1773 }
1774
1775 return status;
1776 }
1777
1778
1779 /*
1780 * If base64-encoded content has a text trailer, return the location, relative
1781 * to c->c_begin, where the valid base64 ends. And return the trailer in the
1782 * addresses pointed to by remainderp. The caller is responsible for
1783 * deallocating that. If no text trailer, return ct->c_end - ct->c_begin and
1784 * leave remainderp unchanged.
1785 */
1786 static size_t
1787 get_valid_base64 (CT ct, char **remainderp) {
1788 const size_t len = ct->c_end - ct->c_begin;
1789 char *buf, format[16];
1790 size_t pos;
1791 int fd;
1792
1793 if (! ct->c_fp && ((ct->c_fp = fopen (ct->c_file, "r")) == NULL)) {
1794 advise (ct->c_file, "unable to open for reading");
1795 return NOTOK;
1796 }
1797 if ((fd = fileno (ct->c_fp)) == -1 ||
1798 lseek (fd, ct->c_begin, SEEK_SET) == (off_t) -1) {
1799 advise (ct->c_file, "unable to seek in");
1800 return NOTOK;
1801 }
1802 buf = mh_xmalloc(len + 1);
1803 snprintf(format, sizeof format, "%%%luc", (unsigned long) len);
1804 if (fscanf(ct->c_fp, format, buf) == EOF) {
1805 advise (ct->c_file, "unable to read");
1806 return NOTOK;
1807 }
1808 buf[len] = '\0';
1809
1810 pos = find_invalid_base64_pos(buf);
1811
1812 if (ct->c_begin + pos < (size_t) ct->c_end) {
1813 *remainderp = mh_xstrdup(&buf[pos]);
1814 } else {
1815 pos = ct->c_end - ct->c_begin;
1816 }
1817 free(buf);
1818
1819 return pos;
1820 }
1821
1822
1823 /*
1824 * Find position in byte string of invalid base64 code. Skip individual
1825 * invalid characters because RFC 2045 Sec 6.8 says they should be ignored.
1826 * The motivating use case is a text footer that was mistakenly applied to
1827 * base64 content. Therefore, if any of these is found, return the position
1828 * of:
1829 * 1. The byte (or end) after one or two consecutive pad ('=') bytes.
1830 * 2. The first of a pair of invalid base64 bytes.
1831 *
1832 * If the base64 code is valid, return the position of the null terminator.
1833 *
1834 * encoded - the base64-encoded string
1835 */
1836 static size_t
1837 find_invalid_base64_pos (const char *encoded) {
1838 const char *cp;
1839 size_t pos;
1840 bool found_pad = false;
1841 unsigned int found_invalid = 0;
1842
1843 for (cp = encoded, pos = 0;
1844 *cp && ! found_pad && found_invalid < 2;
1845 ++cp, ++pos) {
1846 if (isspace ((unsigned char) *cp) ||
1847 isalnum ((unsigned char) *cp) ||
1848 *cp == '+' || *cp == '/' || *cp == '=') {
1849 /* Valid base64 byte. */
1850 if (*cp == '=') {
1851 /* "evidence that the end of the data has been reached"
1852 according to RFC 2045 */
1853 found_pad = true;
1854 }
1855 /* Require consecutive invalid bytes. Let decodeBase64() handle
1856 individual ones. */
1857 found_invalid = 0;
1858 } else {
1859 ++found_invalid;
1860 }
1861 }
1862
1863 if (found_pad && *cp && *cp == '=') {
1864 /* Skip over last in pair of ==. */
1865 ++cp, ++pos;
1866 } else if (found_invalid == 2) {
1867 /* If a pair of consecutive invalid bytes, back up to first one. */
1868 --cp, --pos;
1869 --cp, --pos;
1870 }
1871
1872 /* Skip over any trailing whitespace. */
1873 while (*cp && isspace((unsigned char) *cp)) {
1874 ++cp, ++pos;
1875 }
1876
1877 return pos;
1878 }
1879
1880
1881 /*
1882 * Check for valid base64 encoding, and "fix" if invalid.
1883 */
1884 static int
1885 check_base64_encoding (CT *ctp)
1886 {
1887 char *remainder = NULL;
1888 int status = OK;
1889
1890 /* If there's a footer after base64 content, set c_end to before it, and
1891 store the footer in remainder. */
1892 (*ctp)->c_end = (*ctp)->c_begin + get_valid_base64(*ctp, &remainder);
1893
1894 if (remainder != NULL) {
1895 /* Move ct to a subpart of a new multipart/related, and add the
1896 remainder as a new text/plain subpart of it. */
1897 int ignore_message_mods = 0;
1898
1899 status = insert_into_new_mp_mixed(ctp, remainder, &ignore_message_mods);
1900 free(remainder);
1901 }
1902
1903 return status;
1904 }
1905
1906
1907 /*
1908 * Reformat content as plain text.
1909 * Some of the arguments aren't really needed now, but maybe will
1910 * be in the future for other than text types.
1911 */
1912 static int
1913 reformat_part (CT ct, char *file, char *type, char *subtype, int c_type)
1914 {
1915 int output_subtype, output_encoding;
1916 const char *reason = NULL;
1917 char *cp, *cf;
1918 int status;
1919
1920 /* Hacky: this redirects the output from whatever command is used
1921 to show the part to a file. So, the user can't have any output
1922 redirection in that command.
1923 Could show_multi() in mhshowsbr.c avoid this? */
1924
1925 /* Check for invo_name-format-type/subtype. */
1926 if ((cf = context_find_by_type ("format", type, subtype)) == NULL) {
1927 if (verbosw) {
1928 inform("Don't know how to convert %s, there is no "
1929 "%s-format-%s/%s profile entry",
1930 ct->c_file, invo_name, type, subtype);
1931 }
1932 return NOTOK;
1933 }
1934 if (strchr (cf, '>')) {
1935 inform("'>' prohibited in \"%s\",\nplease fix your "
1936 "%s-format-%s/%s profile entry", cf, invo_name, type,
1937 FENDNULL(subtype));
1938
1939 return NOTOK;
1940 }
1941
1942 cp = concat (cf, " >", file, NULL);
1943 status = show_content_aux (ct, 0, cp, NULL, NULL);
1944 free (cp);
1945
1946 /* Unlink decoded content tmp file and free its filename to avoid
1947 leaks. The file stream should already have been closed. */
1948 if (ct->c_cefile.ce_unlink) {
1949 (void) m_unlink (ct->c_cefile.ce_file);
1950 free (ct->c_cefile.ce_file);
1951 ct->c_cefile.ce_file = NULL;
1952 ct->c_cefile.ce_unlink = 0;
1953 }
1954
1955 if (c_type == CT_TEXT) {
1956 output_subtype = TEXT_PLAIN;
1957 } else {
1958 /* Set subtype to 0, which is always an UNKNOWN subtype. */
1959 output_subtype = 0;
1960 }
1961
1962 output_encoding = content_encoding (ct, &reason);
1963 if (status == OK &&
1964 set_ct_type (ct, c_type, output_subtype, output_encoding) == OK) {
1965 ct->c_cefile.ce_file = file;
1966 ct->c_cefile.ce_unlink = 1;
1967 } else {
1968 ct->c_cefile.ce_unlink = 0;
1969 status = NOTOK;
1970 }
1971
1972 return status;
1973 }
1974
1975
1976 /*
1977 * Create and fill in a multipart part.
1978 */
1979 static CT
1980 build_multipart (CT first_part, CT new_part, int type, int subtype)
1981 {
1982 char *boundary_prefix = "----=_nmh-multipart";
1983 static unsigned int bp_uses = 0;
1984 char bp_uses_buf[16];
1985 char *boundary;
1986 char *boundary_indicator = "; boundary=";
1987 char *typename, *subtypename, *name;
1988 CT ct;
1989 struct part *p;
1990 struct multipart *m;
1991 const struct str2init *ctinit;
1992
1993 NEW0(ct);
1994
1995 if (bp_uses > 0) {
1996 snprintf(bp_uses_buf, sizeof bp_uses_buf - 1, "-%d", bp_uses++);
1997 } else {
1998 bp_uses_buf[0] = '\0';
1999 }
2000 boundary =
2001 concat (boundary_prefix, bp_uses_buf, first_part->c_partno, NULL);
2002
2003 /* Set up the multipart part. These fields of *ct were
2004 initialized to 0 by mh_xcalloc():
2005 c_fp, c_unlink, c_begin, c_end,
2006 c_vrsn, c_ctline, c_celine,
2007 c_id, c_descr, c_dispo, c_partno,
2008 c_ctinfo.ci_comment, c_ctinfo.ci_magic,
2009 c_cefile, c_encoding,
2010 c_digested, c_digest[16], c_ctexbody,
2011 c_ctinitfnx, c_ceopenfnx, c_ceclosefnx, c_cesizefnx,
2012 c_umask, c_rfc934,
2013 c_showproc, c_termproc, c_storeproc, c_storage, c_folder
2014 */
2015
2016 ct->c_file = mh_xstrdup (first_part->c_file);
2017 ct->c_type = type;
2018 ct->c_subtype = subtype;
2019
2020 ctinit = get_ct_init (ct->c_type);
2021
2022 typename = ct_type_str (type);
2023 subtypename = ct_subtype_str (type, subtype);
2024
2025 {
2026 int serial = 0;
2027 int found_boundary = 1;
2028
2029 while (found_boundary && serial < 1000000) {
2030 found_boundary = 0;
2031
2032 /* Ensure that the boundary doesn't appear in the decoded
2033 content. */
2034 if (new_part->c_cefile.ce_file) {
2035 if ((found_boundary =
2036 boundary_in_content (&new_part->c_cefile.ce_fp,
2037 new_part->c_cefile.ce_file,
2038 boundary)) == NOTOK) {
2039 goto return_null;
2040 }
2041 }
2042
2043 /* Ensure that the boundary doesn't appear in the encoded
2044 content. */
2045 if (! found_boundary && new_part->c_file) {
2046 if ((found_boundary =
2047 boundary_in_content (&new_part->c_fp,
2048 new_part->c_file,
2049 boundary)) == NOTOK) {
2050 goto return_null;
2051 }
2052 }
2053
2054 if (found_boundary) {
2055 /* Try a slightly different boundary. */
2056 char buffer2[16];
2057
2058 free (boundary);
2059 ++serial;
2060 snprintf (buffer2, sizeof buffer2, "%d", serial);
2061 boundary =
2062 concat (boundary_prefix,
2063 FENDNULL(first_part->c_partno),
2064 "-", buffer2, NULL);
2065 }
2066 }
2067
2068 if (found_boundary) {
2069 inform("giving up trying to find a unique boundary");
2070 goto return_null;
2071 }
2072 }
2073
2074 name = concat (" ", typename, "/", subtypename, boundary_indicator, "\"",
2075 boundary, "\"", NULL);
2076
2077 /* Load c_first_hf and c_last_hf. */
2078 transfer_noncontent_headers (first_part, ct);
2079 add_header (ct, mh_xstrdup (TYPE_FIELD), concat (name, "\n", NULL));
2080 free (name);
2081
2082 /* Load c_partno. */
2083 if (first_part->c_partno) {
2084 ct->c_partno = mh_xstrdup (first_part->c_partno);
2085 free (first_part->c_partno);
2086 first_part->c_partno = concat (ct->c_partno, ".1", NULL);
2087 new_part->c_partno = concat (ct->c_partno, ".2", NULL);
2088 } else {
2089 first_part->c_partno = mh_xstrdup ("1");
2090 new_part->c_partno = mh_xstrdup ("2");
2091 }
2092
2093 if (ctinit) {
2094 ct->c_ctinfo.ci_type = mh_xstrdup (typename);
2095 ct->c_ctinfo.ci_subtype = mh_xstrdup (subtypename);
2096 }
2097
2098 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
2099 "boundary", boundary, 0);
2100
2101 NEW(p);
2102 NEW(p->mp_next);
2103 p->mp_next->mp_next = NULL;
2104 p->mp_next->mp_part = first_part;
2105
2106 NEW0(m);
2107 m->mp_start = concat (boundary, "\n", NULL);
2108 m->mp_stop = concat (boundary, "--\n", NULL);
2109 m->mp_parts = p;
2110 ct->c_ctparams = m;
2111
2112 free (boundary);
2113
2114 return ct;
2115
2116 return_null:
2117 free_content(ct);
2118 free(boundary);
2119 return NULL;
2120 }
2121
2122
2123 /*
2124 * Check that the boundary does not appear in the content.
2125 */
2126 static int
2127 boundary_in_content (FILE **fp, char *file, const char *boundary)
2128 {
2129 char buffer[NMH_BUFSIZ];
2130 size_t bytes_read;
2131 bool found_boundary = false;
2132
2133 /* free_content() will close *fp if we fopen it here. */
2134 if (! *fp && (*fp = fopen (file, "r")) == NULL) {
2135 advise (file, "unable to open %s for reading", file);
2136 return NOTOK;
2137 }
2138
2139 fseeko (*fp, 0L, SEEK_SET);
2140 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) > 0) {
2141 if (find_str (buffer, bytes_read, boundary)) {
2142 found_boundary = true;
2143 break;
2144 }
2145 }
2146
2147 return found_boundary;
2148 }
2149
2150
2151 /*
2152 * Remove all non-Content headers.
2153 */
2154 static void
2155 transfer_noncontent_headers (CT old, CT new)
2156 {
2157 HF hp, hp_prev;
2158
2159 hp_prev = hp = old->c_first_hf;
2160 while (hp) {
2161 HF next = hp->next;
2162
2163 if (strncasecmp (XXX_FIELD_PRF, hp->name, LEN(XXX_FIELD_PRF))) {
2164 if (hp == old->c_last_hf) {
2165 if (hp == old->c_first_hf) {
2166 old->c_last_hf = old->c_first_hf = NULL;
2167 } else {
2168 hp_prev->next = NULL;
2169 old->c_last_hf = hp_prev;
2170 }
2171 } else {
2172 if (hp == old->c_first_hf) {
2173 old->c_first_hf = next;
2174 } else {
2175 hp_prev->next = next;
2176 }
2177 }
2178
2179 /* Put node hp in the new CT. */
2180 if (new->c_first_hf == NULL) {
2181 new->c_first_hf = hp;
2182 } else {
2183 new->c_last_hf->next = hp;
2184 }
2185 new->c_last_hf = hp;
2186 } else {
2187 /* A Content- header, leave in old. */
2188 hp_prev = hp;
2189 }
2190
2191 hp = next;
2192 }
2193 }
2194
2195
2196 /*
2197 * Set content type.
2198 */
2199 static int
2200 set_ct_type (CT ct, int type, int subtype, int encoding)
2201 {
2202 char *typename = ct_type_str (type);
2203 char *subtypename = ct_subtype_str (type, subtype);
2204 /* E.g, " text/plain" */
2205 char *type_subtypename = concat (" ", typename, "/", subtypename, NULL);
2206 /* E.g, " text/plain\n" */
2207 char *name_plus_nl = concat (type_subtypename, "\n", NULL);
2208 bool found_content_type = false;
2209 HF hf;
2210 const char *cp = NULL;
2211 char *ctline;
2212 int status;
2213
2214 /* Update/add Content-Type header field. */
2215 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2216 if (! strcasecmp (TYPE_FIELD, hf->name)) {
2217 found_content_type = true;
2218 free (hf->value);
2219 hf->value = (cp = strchr (ct->c_ctline, ';'))
2220 ? concat (type_subtypename, cp, "\n", NULL)
2221 : mh_xstrdup (name_plus_nl);
2222 }
2223 }
2224 if (! found_content_type) {
2225 add_header (ct, mh_xstrdup (TYPE_FIELD),
2226 (cp = strchr (ct->c_ctline, ';'))
2227 ? concat (type_subtypename, cp, "\n", NULL)
2228 : mh_xstrdup (name_plus_nl));
2229 }
2230
2231 /* Some of these might not be used, but set them anyway. */
2232 ctline = cp
2233 ? concat (type_subtypename, cp, NULL)
2234 : concat (type_subtypename, NULL);
2235 free (ct->c_ctline);
2236 ct->c_ctline = ctline;
2237 /* Leave other ctinfo members as they were. */
2238 free (ct->c_ctinfo.ci_type);
2239 ct->c_ctinfo.ci_type = mh_xstrdup (typename);
2240 free (ct->c_ctinfo.ci_subtype);
2241 ct->c_ctinfo.ci_subtype = mh_xstrdup (subtypename);
2242 ct->c_type = type;
2243 ct->c_subtype = subtype;
2244
2245 free (name_plus_nl);
2246 free (type_subtypename);
2247
2248 status = set_ce (ct, encoding);
2249
2250 return status;
2251 }
2252
2253
2254 /*
2255 * It's not necessary to update the charset parameter of a Content-Type
2256 * header for a text part. According to RFC 2045 Sec. 6.4, the body
2257 * (content) was originally in the specified charset, "and will be in
2258 * that character set again after decoding."
2259 */
2260 static int
2261 decode_text_parts (CT ct, int encoding, const char *decodetypes,
2262 int *message_mods)
2263 {
2264 int status = OK;
2265 int lf_line_endings = 0;
2266
2267 switch (ct->c_type) {
2268 case CT_MULTIPART: {
2269 struct multipart *m = (struct multipart *) ct->c_ctparams;
2270 struct part *part;
2271
2272 /* Should check to see if the body for this part is encoded?
2273 For now, it gets passed along as-is by InitMultiPart(). */
2274 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2275 status = decode_text_parts (part->mp_part, encoding, decodetypes,
2276 message_mods);
2277 }
2278 break;
2279 }
2280
2281 case CT_MESSAGE:
2282 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2283 struct exbody *e = (struct exbody *) ct->c_ctparams;
2284
2285 status = decode_text_parts (e->eb_content, encoding, decodetypes,
2286 message_mods);
2287 }
2288 break;
2289
2290 default:
2291 if (! should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
2292 break;
2293 }
2294
2295 lf_line_endings =
2296 ct->c_ctparams && ((struct text *) ct->c_ctparams)->lf_line_endings;
2297
2298 switch (ct->c_encoding) {
2299 case CE_BASE64:
2300 case CE_QUOTED: {
2301 int ct_encoding;
2302
2303 if (decode_part (ct) == OK && ct->c_cefile.ce_file) {
2304 const char *reason = NULL;
2305
2306 if ((ct_encoding = content_encoding (ct, &reason)) == CE_BINARY
2307 && encoding != CE_BINARY) {
2308 /* The decoding isn't acceptable so discard it.
2309 Leave status as OK to allow other transformations. */
2310 if (verbosw) {
2311 report (NULL, ct->c_partno, ct->c_file,
2312 "will not decode%s because it is binary (%s)",
2313 ct->c_partno ? ""
2314 : (FENDNULL(ct->c_ctline)),
2315 reason);
2316 }
2317 (void) m_unlink (ct->c_cefile.ce_file);
2318 free (ct->c_cefile.ce_file);
2319 ct->c_cefile.ce_file = NULL;
2320 } else if (ct->c_encoding == CE_QUOTED &&
2321 ct_encoding == CE_8BIT && encoding == CE_7BIT) {
2322 /* The decoding isn't acceptable so discard it.
2323 Leave status as OK to allow other transformations. */
2324 if (verbosw) {
2325 report (NULL, ct->c_partno, ct->c_file,
2326 "will not decode%s because it is 8bit",
2327 ct->c_partno ? ""
2328 : (FENDNULL(ct->c_ctline)));
2329 }
2330 (void) m_unlink (ct->c_cefile.ce_file);
2331 free (ct->c_cefile.ce_file);
2332 ct->c_cefile.ce_file = NULL;
2333 } else {
2334 int enc;
2335
2336 if (ct_encoding == CE_BINARY) {
2337 enc = CE_BINARY;
2338 } else if (ct_encoding == CE_8BIT && encoding == CE_7BIT) {
2339 enc = CE_QUOTED;
2340 } else {
2341 enc = ct_encoding;
2342 }
2343 if (set_ce (ct, enc) == OK) {
2344 ++*message_mods;
2345 if (verbosw) {
2346 report (NULL, ct->c_partno, ct->c_file, "decode%s",
2347 FENDNULL(ct->c_ctline));
2348 }
2349 if (lf_line_endings) {
2350 strip_crs (ct, message_mods);
2351 }
2352 } else {
2353 status = NOTOK;
2354 }
2355 }
2356 } else {
2357 status = NOTOK;
2358 }
2359 break;
2360 }
2361 case CE_8BIT:
2362 case CE_7BIT:
2363 if (lf_line_endings) {
2364 strip_crs (ct, message_mods);
2365 }
2366 break;
2367 default:
2368 break;
2369 }
2370
2371 break;
2372 }
2373
2374 return status;
2375 }
2376
2377
2378 /*
2379 * Determine if the part with type[/subtype] should be decoded, according to
2380 * decodetypes (which came from the -decodetypes switch).
2381 */
2382 static int
2383 should_decode(const char *decodetypes, const char *type, const char *subtype)
2384 {
2385 /* Quick search for matching type[/subtype] in decodetypes: bracket
2386 decodetypes with commas, then search for ,type, and ,type/subtype, in
2387 it. */
2388
2389 bool found_match = false;
2390 char *delimited_decodetypes = concat(",", decodetypes, ",", NULL);
2391 char *delimited_type = concat(",", type, ",", NULL);
2392
2393 if (nmh_strcasestr(delimited_decodetypes, delimited_type)) {
2394 found_match = true;
2395 } else if (subtype != NULL) {
2396 char *delimited_type_subtype =
2397 concat(",", type, "/", subtype, ",", NULL);
2398
2399 if (nmh_strcasestr(delimited_decodetypes, delimited_type_subtype)) {
2400 found_match = true;
2401 }
2402 free(delimited_type_subtype);
2403 }
2404
2405 free(delimited_type);
2406 free(delimited_decodetypes);
2407
2408 return found_match;
2409 }
2410
2411
2412 /*
2413 * See if the decoded content is 7bit, 8bit, or binary. It's binary
2414 * if it has any NUL characters, a CR not followed by a LF, or lines
2415 * greater than 998 characters in length. If binary, reason is set
2416 * to a string explaining why.
2417 */
2418 static int
2419 content_encoding (CT ct, const char **reason)
2420 {
2421 CE ce = &ct->c_cefile;
2422 int encoding = CE_7BIT;
2423
2424 if (ce->ce_file) {
2425 size_t line_len = 0;
2426 char buffer[NMH_BUFSIZ];
2427 size_t inbytes;
2428
2429 if (! ce->ce_fp && (ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2430 advise (ce->ce_file, "unable to open for reading");
2431 return CE_UNKNOWN;
2432 }
2433
2434 fseeko (ce->ce_fp, 0L, SEEK_SET);
2435 while (encoding != CE_BINARY &&
2436 (inbytes = fread (buffer, 1, sizeof buffer, ce->ce_fp)) > 0) {
2437 char *cp;
2438 size_t i;
2439 int last_char_was_cr = 0;
2440
2441 for (i = 0, cp = buffer; i < inbytes; ++i, ++cp) {
2442 if (*cp == '\0' || ++line_len > 998 ||
2443 (*cp != '\n' && last_char_was_cr)) {
2444 encoding = CE_BINARY;
2445 if (*cp == '\0') {
2446 *reason = "null character";
2447 } else if (line_len > 998) {
2448 *reason = "line length > 998";
2449 } else if (*cp != '\n' && last_char_was_cr) {
2450 *reason = "CR not followed by LF";
2451 } else {
2452 /* Should not reach this. */
2453 *reason = "";
2454 }
2455 break;
2456 }
2457 if (*cp == '\n') {
2458 line_len = 0;
2459 } else if (! isascii ((unsigned char) *cp)) {
2460 encoding = CE_8BIT;
2461 }
2462
2463 last_char_was_cr = *cp == '\r';
2464 }
2465 }
2466
2467 fclose (ce->ce_fp);
2468 ce->ce_fp = NULL;
2469 } /* else should never happen */
2470
2471 return encoding;
2472 }
2473
2474
2475 /*
2476 * Strip carriage returns from content.
2477 */
2478 static int
2479 strip_crs (CT ct, int *message_mods)
2480 {
2481 char *charset = content_charset (ct);
2482 int status = OK;
2483
2484 /* Only strip carriage returns if content is ASCII or another
2485 charset that has the same readily recognizable CR followed by a
2486 LF. We can include UTF-8 here because if the high-order bit of
2487 a UTF-8 byte is 0, then it must be a single-byte ASCII
2488 character. */
2489 if (! strcasecmp (charset, "US-ASCII") ||
2490 ! strcasecmp (charset, "UTF-8") ||
2491 ! strncasecmp (charset, "ISO-8859-", 9) ||
2492 ! strncasecmp (charset, "WINDOWS-12", 10)) {
2493 char **file = NULL;
2494 FILE **fp = NULL;
2495 size_t begin;
2496 size_t end;
2497 bool has_crs = false;
2498 bool opened_input_file = false;
2499
2500 if (ct->c_cefile.ce_file) {
2501 file = &ct->c_cefile.ce_file;
2502 fp = &ct->c_cefile.ce_fp;
2503 begin = end = 0;
2504 } else if (ct->c_file) {
2505 file = &ct->c_file;
2506 fp = &ct->c_fp;
2507 begin = (size_t) ct->c_begin;
2508 end = (size_t) ct->c_end;
2509 } /* else don't know where the content is */
2510
2511 if (file && *file && fp) {
2512 if (! *fp) {
2513 if ((*fp = fopen (*file, "r")) == NULL) {
2514 advise (*file, "unable to open for reading");
2515 status = NOTOK;
2516 } else {
2517 opened_input_file = true;
2518 }
2519 }
2520 }
2521
2522 if (fp && *fp) {
2523 char buffer[NMH_BUFSIZ];
2524 size_t bytes_read;
2525 size_t bytes_to_read =
2526 end > 0 && end > begin ? end - begin : sizeof buffer;
2527
2528 fseeko (*fp, begin, SEEK_SET);
2529 while ((bytes_read = fread (buffer, 1,
2530 min (bytes_to_read, sizeof buffer),
2531 *fp)) > 0) {
2532 /* Look for CR followed by a LF. This is supposed to
2533 be text so there should be LF's. If not, don't
2534 modify the content. */
2535 char *cp;
2536 size_t i;
2537 bool last_char_was_cr = false;
2538
2539 if (end > 0) { bytes_to_read -= bytes_read; }
2540
2541 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
2542 if (*cp == '\n' && last_char_was_cr) {
2543 has_crs = true;
2544 break;
2545 }
2546
2547 last_char_was_cr = *cp == '\r';
2548 }
2549 }
2550
2551 if (has_crs) {
2552 int fd;
2553 char *stripped_content_file;
2554 char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL);
2555
2556 if (tempfile == NULL) {
2557 die("unable to create temporary file in %s",
2558 get_temp_dir());
2559 }
2560 stripped_content_file = mh_xstrdup (tempfile);
2561
2562 /* Strip each CR before a LF from the content. */
2563 fseeko (*fp, begin, SEEK_SET);
2564 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) >
2565 0) {
2566 char *cp;
2567 size_t i;
2568 bool last_char_was_cr = false;
2569
2570 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
2571 if (*cp == '\r') {
2572 last_char_was_cr = true;
2573 } else if (last_char_was_cr) {
2574 if (*cp != '\n') {
2575 if (write (fd, "\r", 1) < 0) {
2576 advise (tempfile, "CR write");
2577 }
2578 }
2579 if (write (fd, cp, 1) < 0) {
2580 advise (tempfile, "write");
2581 }
2582 last_char_was_cr = false;
2583 } else {
2584 if (write (fd, cp, 1) < 0) {
2585 advise (tempfile, "write");
2586 }
2587 last_char_was_cr = false;
2588 }
2589 }
2590 }
2591
2592 if (close (fd)) {
2593 inform("unable to write temporary file %s, continuing...",
2594 stripped_content_file);
2595 (void) m_unlink (stripped_content_file);
2596 free(stripped_content_file);
2597 status = NOTOK;
2598 } else {
2599 /* Replace the decoded file with the converted one. */
2600 if (ct->c_cefile.ce_file && ct->c_cefile.ce_unlink)
2601 (void) m_unlink (ct->c_cefile.ce_file);
2602
2603 free(ct->c_cefile.ce_file);
2604 ct->c_cefile.ce_file = stripped_content_file;
2605 ct->c_cefile.ce_unlink = 1;
2606
2607 ++*message_mods;
2608 if (verbosw) {
2609 report (NULL, ct->c_partno,
2610 begin == 0 && end == 0 ? "" : *file,
2611 "stripped CRs");
2612 }
2613 }
2614 }
2615
2616 if (opened_input_file) {
2617 fclose (*fp);
2618 *fp = NULL;
2619 }
2620 }
2621 }
2622
2623 free (charset);
2624
2625 return status;
2626 }
2627
2628
2629 /*
2630 * Add/update, if necessary, the message C-T-E, based on the least restrictive
2631 * of the part C-T-E's.
2632 */
2633 static void
2634 update_cte (CT ct)
2635 {
2636 const int least_restrictive_enc = least_restrictive_encoding (ct);
2637
2638 if (least_restrictive_enc != CE_UNKNOWN &&
2639 least_restrictive_enc != CE_7BIT) {
2640 char *cte = concat (" ", ce_str (least_restrictive_enc), "\n", NULL);
2641 HF hf;
2642 bool found_cte = false;
2643
2644 /* Update/add Content-Transfer-Encoding header field. */
2645 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2646 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
2647 found_cte = true;
2648 free (hf->value);
2649 hf->value = cte;
2650 }
2651 }
2652 if (! found_cte) {
2653 add_header (ct, mh_xstrdup (ENCODING_FIELD), cte);
2654 }
2655 }
2656 }
2657
2658
2659 /*
2660 * Find the least restrictive encoding (7bit, 8bit, binary) of the parts
2661 * within a message.
2662 */
2663 static int
2664 least_restrictive_encoding (CT ct)
2665 {
2666 int encoding = CE_UNKNOWN;
2667
2668 switch (ct->c_type) {
2669 case CT_MULTIPART: {
2670 struct multipart *m = (struct multipart *) ct->c_ctparams;
2671 struct part *part;
2672
2673 for (part = m->mp_parts; part; part = part->mp_next) {
2674 const int part_encoding =
2675 least_restrictive_encoding (part->mp_part);
2676
2677 if (less_restrictive (encoding, part_encoding)) {
2678 encoding = part_encoding;
2679 }
2680 }
2681 break;
2682 }
2683
2684 case CT_MESSAGE:
2685 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2686 struct exbody *e = (struct exbody *) ct->c_ctparams;
2687 const int part_encoding =
2688 least_restrictive_encoding (e->eb_content);
2689
2690 if (less_restrictive (encoding, part_encoding)) {
2691 encoding = part_encoding;
2692 }
2693 }
2694 break;
2695
2696 default: {
2697 if (less_restrictive (encoding, ct->c_encoding)) {
2698 encoding = ct->c_encoding;
2699 }
2700 }}
2701
2702 return encoding;
2703 }
2704
2705
2706 /*
2707 * Return whether the second encoding is less restrictive than the first, where
2708 * "less restrictive" is in the sense used by RFC 2045 Secs. 6.1 and 6.4. So,
2709 * CE_BINARY is less restrictive than CE_8BIT and
2710 * CE_8BIT is less restrictive than CE_7BIT.
2711 */
2712 static int
2713 less_restrictive (int encoding, int second_encoding)
2714 {
2715 switch (second_encoding) {
2716 case CE_BINARY:
2717 return encoding != CE_BINARY;
2718 case CE_8BIT:
2719 return encoding != CE_BINARY && encoding != CE_8BIT;
2720 case CE_7BIT:
2721 return encoding != CE_BINARY && encoding != CE_8BIT &&
2722 encoding != CE_7BIT;
2723 default :
2724 return 0;
2725 }
2726 }
2727
2728
2729 /*
2730 * Convert character set of each part.
2731 */
2732 static int
2733 convert_charsets (CT ct, char *dest_charset, int *message_mods)
2734 {
2735 int status = OK;
2736
2737 switch (ct->c_type) {
2738 case CT_TEXT:
2739 if (ct->c_subtype == TEXT_PLAIN) {
2740 status = convert_charset (ct, dest_charset, message_mods);
2741 if (status == OK) {
2742 if (verbosw) {
2743 char *ct_charset = content_charset (ct);
2744
2745 report (NULL, ct->c_partno, ct->c_file,
2746 "convert %s to %s", ct_charset, dest_charset);
2747 free (ct_charset);
2748 }
2749 } else {
2750 char *ct_charset = content_charset (ct);
2751
2752 report ("iconv", ct->c_partno, ct->c_file,
2753 "failed to convert %s to %s", ct_charset, dest_charset);
2754 free (ct_charset);
2755 }
2756 }
2757 break;
2758
2759 case CT_MULTIPART: {
2760 struct multipart *m = (struct multipart *) ct->c_ctparams;
2761 struct part *part;
2762
2763 /* Should check to see if the body for this part is encoded?
2764 For now, it gets passed along as-is by InitMultiPart(). */
2765 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2766 status =
2767 convert_charsets (part->mp_part, dest_charset, message_mods);
2768 }
2769 break;
2770 }
2771
2772 case CT_MESSAGE:
2773 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2774 struct exbody *e = (struct exbody *) ct->c_ctparams;
2775
2776 status =
2777 convert_charsets (e->eb_content, dest_charset, message_mods);
2778 }
2779 break;
2780
2781 default:
2782 break;
2783 }
2784
2785 return status;
2786 }
2787
2788
2789 /*
2790 * Fix various problems that aren't handled elsewhere. These
2791 * are fixed unconditionally: there are no switches to disable
2792 * them. Currently, "problems" are these:
2793 * 1) remove extraneous semicolon at the end of a header parameter list
2794 * 2) replace RFC 2047 encoding with RFC 2231 encoding of name and
2795 * filename parameters in Content-Type and Content-Disposition
2796 * headers, respectively.
2797 */
2798 static int
2799 fix_always (CT *ctp, const fix_transformations *fx, int *message_mods)
2800 {
2801 int status = OK;
2802
2803 switch ((*ctp)->c_type) {
2804 case CT_MULTIPART: {
2805 struct multipart *m = (struct multipart *) (*ctp)->c_ctparams;
2806 struct part *part;
2807
2808 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2809 status = fix_always (&part->mp_part, fx, message_mods);
2810 }
2811 break;
2812 }
2813
2814 case CT_MESSAGE:
2815 if ((*ctp)->c_subtype == MESSAGE_EXTERNAL) {
2816 struct exbody *e = (struct exbody *) (*ctp)->c_ctparams;
2817
2818 status = fix_always (&e->eb_content, fx, message_mods);
2819 }
2820 break;
2821
2822 default: {
2823 HF hf;
2824
2825 if ((*ctp)->c_first_hf) {
2826 fix_filename_encoding (*ctp);
2827 }
2828
2829 for (hf = (*ctp)->c_first_hf; hf; hf = hf->next) {
2830 size_t len = strlen (hf->value);
2831
2832 if (strcasecmp (hf->name, TYPE_FIELD) != 0 &&
2833 strcasecmp (hf->name, DISPO_FIELD) != 0) {
2834 /* Only do this for Content-Type and
2835 Content-Disposition fields because those are the
2836 only headers that parse_mime() warns about. */
2837 continue;
2838 }
2839
2840 /* whitespace following a trailing ';' will be nuked as well */
2841 if (hf->value[len - 1] == '\n') {
2842 while (isspace((unsigned char)(hf->value[len - 2]))) {
2843 if (len-- == 0) { break; }
2844 }
2845 }
2846
2847 if (hf->value[len - 2] == ';') {
2848 /* Remove trailing ';' from parameter value. */
2849 hf->value[len - 2] = '\n';
2850 hf->value[len - 1] = '\0';
2851
2852 /* Also, if Content-Type parameter, remove trailing ';'
2853 from (*ctp)->c_ctline. This probably isn't necessary
2854 but can't hurt. */
2855 if (strcasecmp(hf->name, TYPE_FIELD) == 0 && (*ctp)->c_ctline) {
2856 size_t l = strlen((*ctp)->c_ctline) - 1;
2857 while (isspace((unsigned char)((*ctp)->c_ctline[l])) ||
2858 (*ctp)->c_ctline[l] == ';') {
2859 (*ctp)->c_ctline[l--] = '\0';
2860 if (l == 0) { break; }
2861 }
2862 }
2863
2864 ++*message_mods;
2865 if (verbosw) {
2866 report (NULL, (*ctp)->c_partno, (*ctp)->c_file,
2867 "remove trailing ; from %s parameter value",
2868 hf->name);
2869 }
2870 }
2871 }
2872
2873 if (fx->checkbase64 && (*ctp)->c_encoding == CE_BASE64) {
2874 status = check_base64_encoding (ctp);
2875 }
2876 }}
2877
2878 return status;
2879 }
2880
2881
2882 /*
2883 * Decodes UTF-8 encoded header values. Similar to fix_filename_param(), but
2884 * does not modify any MIME parameter values.
2885 */
2886 static int
2887 decode_header_field_bodies (CT ct, int *message_mods)
2888 {
2889 int status = OK;
2890
2891 switch (ct->c_type) {
2892 case CT_MULTIPART: {
2893 struct multipart *m = (struct multipart *) ct->c_ctparams;
2894 struct part *part;
2895
2896 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2897 status = decode_header_field_bodies (part->mp_part, message_mods);
2898 }
2899 break;
2900 }
2901
2902 case CT_MESSAGE:
2903 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2904 struct exbody *e = (struct exbody *) ct->c_ctparams;
2905
2906 status = decode_header_field_bodies (e->eb_content, message_mods);
2907 }
2908 break;
2909 }
2910
2911 HF hf;
2912
2913 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2914 /* Only decode UTF-8 values. */
2915 if (hf->value && has_suffix(hf->value, "?=\n") &&
2916 (! strncasecmp (hf->value, " =?utf8?", 8) ||
2917 ! strncasecmp (hf->value, " =?utf-8?", 9))) {
2918 /* Looks like an RFC 2047 encoded parameter. */
2919 char decoded[PATH_MAX + 1];
2920
2921 if (decode_rfc2047 (hf->value, decoded, sizeof decoded)) {
2922 const size_t len = strlen(decoded);
2923
2924 /* decode_rfc2047() could truncate if the buffer fills up.
2925 Detect and discard if that happened. */
2926 if (len < sizeof(decoded) - 1 && strcmp(hf->value, decoded)) {
2927 hf->value = mh_xrealloc (hf->value, len + 1);
2928 strncpy (hf->value, decoded, len + 1);
2929 ++*message_mods;
2930 }
2931 } else {
2932 inform("failed to decode %s parameter %s", hf->name, hf->value);
2933 status = NOTOK;
2934 }
2935 }
2936 }
2937
2938 return status;
2939 }
2940
2941
2942 /*
2943 * Factor out common code for loops in fix_filename_encoding().
2944 */
2945 static int
2946 fix_filename_param (char *name, char *value, PM *first_pm, PM *last_pm)
2947 {
2948 bool fixed = false;
2949
2950 if (has_prefix(value, "=?") && has_suffix(value, "?=")) {
2951 /* Looks like an RFC 2047 encoded parameter. */
2952 char decoded[PATH_MAX + 1];
2953
2954 if (decode_rfc2047 (value, decoded, sizeof decoded)) {
2955 /* Encode using RFC 2231. */
2956 replace_param (first_pm, last_pm, name, decoded, 0);
2957 fixed = true;
2958 } else {
2959 inform("failed to decode %s parameter %s", name, value);
2960 }
2961 }
2962
2963 return fixed;
2964 }
2965
2966
2967 /*
2968 * Replace RFC 2047 encoding with RFC 2231 encoding of name and
2969 * filename parameters in Content-Type and Content-Disposition
2970 * headers, respectively.
2971 */
2972 static int
2973 fix_filename_encoding (CT ct)
2974 {
2975 PM pm;
2976 HF hf;
2977 int fixed = 0;
2978
2979 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
2980 if (pm->pm_name && pm->pm_value &&
2981 strcasecmp (pm->pm_name, "name") == 0) {
2982 fixed = fix_filename_param (pm->pm_name, pm->pm_value,
2983 &ct->c_ctinfo.ci_first_pm,
2984 &ct->c_ctinfo.ci_last_pm);
2985 }
2986 }
2987
2988 for (pm = ct->c_dispo_first; pm; pm = pm->pm_next) {
2989 if (pm->pm_name && pm->pm_value &&
2990 strcasecmp (pm->pm_name, "filename") == 0) {
2991 fixed = fix_filename_param (pm->pm_name, pm->pm_value,
2992 &ct->c_dispo_first,
2993 &ct->c_dispo_last);
2994 }
2995 }
2996
2997 /* Fix hf values to correspond. */
2998 for (hf = ct->c_first_hf; fixed && hf; hf = hf->next) {
2999 enum { OTHER, TYPE_HEADER, DISPO_HEADER } field = OTHER;
3000
3001 if (strcasecmp (hf->name, TYPE_FIELD) == 0) {
3002 field = TYPE_HEADER;
3003 } else if (strcasecmp (hf->name, DISPO_FIELD) == 0) {
3004 field = DISPO_HEADER;
3005 }
3006
3007 if (field != OTHER) {
3008 const char *const semicolon_loc = strchr (hf->value, ';');
3009
3010 if (semicolon_loc) {
3011 const size_t len =
3012 strlen (hf->name) + 1 + semicolon_loc - hf->value;
3013 const char *const params =
3014 output_params (len,
3015 field == TYPE_HEADER
3016 ? ct->c_ctinfo.ci_first_pm
3017 : ct->c_dispo_first,
3018 NULL, 0);
3019 const char *const new_params = concat (params, "\n", NULL);
3020
3021 replace_substring (&hf->value, semicolon_loc, new_params);
3022 free((void *)new_params); /* Cast away const. Sigh. */
3023 free((void *)params);
3024 } else {
3025 inform("did not find semicolon in %s:%s\n",
3026 hf->name, hf->value);
3027 }
3028 }
3029 }
3030
3031 return OK;
3032 }
3033
3034
3035 /*
3036 * Output content in input file to output file.
3037 */
3038 static int
3039 write_content (CT ct, const char *input_filename, char *outfile, FILE *outfp,
3040 int modify_inplace, int message_mods)
3041 {
3042 int status = OK;
3043
3044 if (modify_inplace) {
3045 if (message_mods > 0) {
3046 if ((status = output_message_fp (ct, outfp, outfile)) == OK) {
3047 char *infile = input_filename
3048 ? mh_xstrdup (input_filename)
3049 : mh_xstrdup (ct->c_file ? ct->c_file : "-");
3050
3051 if (remove_file (infile) == OK) {
3052 if (rename (outfile, infile)) {
3053 /* Rename didn't work, possibly because of an
3054 attempt to rename across filesystems. Try
3055 brute force copy. */
3056 int old = open (outfile, O_RDONLY);
3057 int new =
3058 open (infile, O_WRONLY | O_CREAT, m_gmprot ());
3059 int i = -1;
3060
3061 if (old != -1 && new != -1) {
3062 char buffer[NMH_BUFSIZ];
3063
3064 while ((i = read (old, buffer, sizeof buffer)) >
3065 0) {
3066 if (write (new, buffer, i) != i) {
3067 i = -1;
3068 break;
3069 }
3070 }
3071 }
3072 if (new != -1) { close (new); }
3073 if (old != -1) { close (old); }
3074 (void) m_unlink (outfile);
3075
3076 if (i < 0) {
3077 /* The -file argument processing used path() to
3078 expand filename to absolute path. */
3079 int file = ct->c_file && ct->c_file[0] == '/';
3080
3081 inform("unable to rename %s %s to %s, continuing...",
3082 file ? "file" : "message", outfile,
3083 infile);
3084 status = NOTOK;
3085 }
3086 }
3087 } else {
3088 inform("unable to remove input file %s, "
3089 "not modifying it, continuing...", infile);
3090 (void) m_unlink (outfile);
3091 status = NOTOK;
3092 }
3093
3094 free (infile);
3095 } else {
3096 status = NOTOK;
3097 }
3098 } else {
3099 /* No modifications and didn't need the tmp outfile. */
3100 (void) m_unlink (outfile);
3101 }
3102 } else {
3103 /* Output is going to some file. Produce it whether or not
3104 there were modifications. */
3105 status = output_message_fp (ct, outfp, outfile);
3106 }
3107
3108 flush_errors ();
3109 return status;
3110 }
3111
3112
3113 /*
3114 * parse_mime() does not set lf_line_endings in struct text, so use this
3115 * function to do it. It touches the parts the decodetypes identifies.
3116 */
3117 static void
3118 set_text_ctparams(CT ct, char *decodetypes, int lf_line_endings)
3119 {
3120 switch (ct->c_type) {
3121 case CT_MULTIPART: {
3122 struct multipart *m = (struct multipart *) ct->c_ctparams;
3123 struct part *part;
3124
3125 for (part = m->mp_parts; part; part = part->mp_next) {
3126 set_text_ctparams(part->mp_part, decodetypes, lf_line_endings);
3127 }
3128 break;
3129 }
3130
3131 case CT_MESSAGE:
3132 if (ct->c_subtype == MESSAGE_EXTERNAL) {
3133 struct exbody *e = (struct exbody *) ct->c_ctparams;
3134
3135 set_text_ctparams(e->eb_content, decodetypes, lf_line_endings);
3136 }
3137 break;
3138
3139 default:
3140 if (should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
3141 if (ct->c_ctparams == NULL) {
3142 ct->c_ctparams = mh_xcalloc(1, sizeof (struct text));
3143 }
3144 ((struct text *) ct->c_ctparams)->lf_line_endings = lf_line_endings;
3145 }
3146 }
3147 }
3148
3149
3150 /*
3151 * If "rmmproc" is defined, call that to remove the file. Otherwise,
3152 * use the standard MH backup file.
3153 */
3154 static int
3155 remove_file (const char *file)
3156 {
3157 if (rmmproc) {
3158 char *rmm_command = concat (rmmproc, " ", file, NULL);
3159 int status = system (rmm_command);
3160
3161 free (rmm_command);
3162 return WIFEXITED (status) ? WEXITSTATUS (status) : NOTOK;
3163 }
3164 /* This is OK for a non-message file, it still uses the
3165 BACKUP_PREFIX form. The backup file will be in the same
3166 directory as file. */
3167 return rename (file, m_backup (file));
3168 }
3169
3170
3171 /*
3172 * Output formatted message to user.
3173 */
3174 static void
3175 report (char *what, char *partno, char *filename, char *message, ...)
3176 {
3177 va_list args;
3178 char *fmt;
3179
3180 if (verbosw) {
3181 va_start (args, message);
3182 fmt = concat (filename, partno ? " part " : ", ",
3183 FENDNULL(partno), partno ? ", " : "", message, NULL);
3184
3185 advertise (what, NULL, fmt, args);
3186
3187 free (fmt);
3188 va_end (args);
3189 }
3190 }
3191
3192
3193 static void
3194 pipeser (int i)
3195 {
3196 if (i == SIGQUIT) {
3197 fflush (stdout);
3198 fprintf (stderr, "\n");
3199 fflush (stderr);
3200 }
3201
3202 done (1);
3203 /* NOTREACHED */
3204 }