]> diplodocus.org Git - nmh/blob - uip/mhfixmsg.c
0cf8fe6f8aa5c7fe85c9f59469372c10e5648609
[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", 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", 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");
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",
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 char *hf_value = cpytrim (hf->value);
1081 inform("did not find %s in %s", type, hf_value);
1082 free (hf_value);
1083 }
1084 }
1085 }
1086 free (ct_type_subtype);
1087 }
1088 free (type_subtype);
1089 }
1090 }
1091 }}
1092
1093 return status;
1094 }
1095
1096
1097 /*
1098 * Replace a substring, allocating space to hold the new one.
1099 */
1100 char *
1101 replace_substring (char **str, const char *old, const char *new)
1102 {
1103 char *cp;
1104
1105 if ((cp = strstr (*str, old))) {
1106 char *remainder = cp + strlen (old);
1107 char *prefix, *new_str;
1108
1109 if (cp - *str) {
1110 prefix = mh_xstrdup(*str);
1111 *(prefix + (cp - *str)) = '\0';
1112 new_str = concat (prefix, new, remainder, NULL);
1113 free (prefix);
1114 } else {
1115 new_str = concat (new, remainder, NULL);
1116 }
1117
1118 free (*str);
1119
1120 return *str = new_str;
1121 }
1122
1123 return NULL;
1124 }
1125
1126
1127 /*
1128 * Remove a name=value parameter, given just its name, from a header value.
1129 */
1130 char *
1131 remove_parameter (char *str, const char *name)
1132 {
1133 /* It looks to me, based on the BNF in RFC 2045, than there can't
1134 be whitespace between the parameter name and the "=", or
1135 between the "=" and the parameter value. */
1136 char *param_name = concat (name, "=", NULL);
1137 char *cp;
1138
1139 if ((cp = strstr (str, param_name))) {
1140 char *start, *end;
1141 size_t count = 1;
1142
1143 /* Remove any leading spaces, before the parameter name. */
1144 for (start = cp;
1145 start > str && isspace ((unsigned char) *(start-1));
1146 --start) {
1147 continue;
1148 }
1149 /* Remove a leading semicolon. */
1150 if (start > str && *(start-1) == ';') { --start; }
1151
1152 end = cp + strlen (name) + 1;
1153 if (*end == '"') {
1154 /* Skip past the quoted value, and then the final quote. */
1155 for (++end ; *end && *end != '"'; ++end) { continue; }
1156 ++end;
1157 } else {
1158 /* Skip past the value. */
1159 for (++end ; *end && ! isspace ((unsigned char) *end); ++end) {}
1160 }
1161
1162 /* Count how many characters need to be moved. Include
1163 trailing null, which is accounted for by the
1164 initialization of count to 1. */
1165 for (cp = end; *cp; ++cp) { ++count; }
1166 (void) memmove (start, end, count);
1167 }
1168
1169 free (param_name);
1170
1171 return str;
1172 }
1173
1174
1175 /*
1176 * Fix Content-Transfer-Encoding of composite,, e.g., message or multipart, part.
1177 * According to RFC 2045 Sec. 6.4, it must be 7bit, 8bit, or binary. Set it to
1178 * 8 bit.
1179 */
1180 static int
1181 fix_composite_cte (CT ct, int *message_mods)
1182 {
1183 int status = OK;
1184
1185 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) {
1186 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT &&
1187 ct->c_encoding != CE_BINARY) {
1188 HF hf;
1189
1190 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1191 char *name = hf->name;
1192 for (; isspace((unsigned char)*name); ++name) {
1193 continue;
1194 }
1195
1196 if (! strncasecmp (name, ENCODING_FIELD,
1197 LEN(ENCODING_FIELD))) {
1198 char *prefix = "Nmh-REPLACED-INVALID-";
1199 HF h;
1200
1201 NEW(h);
1202 h->name = mh_xstrdup (hf->name);
1203 h->next = hf->next;
1204 hf->next = h;
1205
1206 /* Retain old header but prefix its name. */
1207 free (hf->name);
1208 hf->name = concat (prefix, h->name, NULL);
1209
1210 ++*message_mods;
1211 if (verbosw) {
1212 char *encoding = cpytrim (hf->value);
1213 report (NULL, ct->c_partno, ct->c_file,
1214 "replace Content-Transfer-Encoding of %s "
1215 "with 8 bit", encoding);
1216 free (encoding);
1217 }
1218
1219 h->value = mh_xstrdup (" 8bit\n");
1220
1221 /* Don't need to warn for multiple C-T-E header
1222 fields, parse_mime() already does that. But
1223 if there are any, fix them all as necessary. */
1224 hf = h;
1225 }
1226 }
1227
1228 set_ce (ct, CE_8BIT);
1229 }
1230
1231 if (ct->c_type == CT_MULTIPART) {
1232 struct multipart *m;
1233 struct part *part;
1234
1235 m = (struct multipart *) ct->c_ctparams;
1236 for (part = m->mp_parts; part; part = part->mp_next) {
1237 if (fix_composite_cte (part->mp_part, message_mods) != OK) {
1238 status = NOTOK;
1239 break;
1240 }
1241 }
1242 }
1243 }
1244
1245 return status;
1246 }
1247
1248
1249 /*
1250 * Set content encoding.
1251 */
1252 static int
1253 set_ce (CT ct, int encoding)
1254 {
1255 const char *ce = ce_str (encoding);
1256 const struct str2init *ctinit = get_ce_method (ce);
1257
1258 if (ctinit) {
1259 char *cte = concat (" ", ce, "\n", NULL);
1260 bool found_cte = false;
1261 HF hf;
1262 /* Decoded contents might be in ct->c_cefile.ce_file, if the
1263 caller is decode_text_parts (). Save because we'll
1264 overwrite below. */
1265 struct cefile decoded_content_info = ct->c_cefile;
1266
1267 ct->c_encoding = encoding;
1268
1269 ct->c_ctinitfnx = ctinit->si_init;
1270 /* This will assign ct->c_cefile with an all-0 struct, which
1271 is what we want. */
1272 (*ctinit->si_init) (ct);
1273 /* After returning, the caller should set
1274 ct->c_cefile.ce_file to the name of the file containing
1275 the contents. */
1276
1277 if (ct->c_ceclosefnx) {
1278 (*ct->c_ceclosefnx) (ct);
1279 }
1280
1281 /* Restore the cefile. */
1282 ct->c_cefile = decoded_content_info;
1283
1284 /* Update/add Content-Transfer-Encoding header field. */
1285 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1286 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
1287 found_cte = true;
1288 free (hf->value);
1289 hf->value = cte;
1290 }
1291 }
1292 if (! found_cte) {
1293 add_header (ct, mh_xstrdup (ENCODING_FIELD), cte);
1294 }
1295
1296 /* Update c_celine. It's used only by mhlist -debug. */
1297 free (ct->c_celine);
1298 ct->c_celine = mh_xstrdup (cte);
1299
1300 return OK;
1301 }
1302
1303 return NOTOK;
1304 }
1305
1306
1307 /*
1308 * Make sure each text part has a corresponding text/plain part.
1309 */
1310 static int
1311 ensure_text_plain (CT *ct, CT parent, int *message_mods, int replacetextplain)
1312 {
1313 int status = OK;
1314
1315 switch ((*ct)->c_type) {
1316 case CT_TEXT: {
1317 /* Nothing to do for text/plain. */
1318 if ((*ct)->c_subtype == TEXT_PLAIN) { return OK; }
1319
1320 if (parent && parent->c_type == CT_MULTIPART &&
1321 parent->c_subtype == MULTI_ALTERNATE) {
1322 int new_subpart_number = 1;
1323 int has_text_plain =
1324 find_textplain_sibling (parent, replacetextplain,
1325 &new_subpart_number);
1326
1327 if (! has_text_plain) {
1328 /* Parent is a multipart/alternative. Insert a new
1329 text/plain subpart. */
1330 const int inserted =
1331 insert_new_text_plain_part (*ct, new_subpart_number,
1332 parent);
1333 if (inserted) {
1334 ++*message_mods;
1335 if (verbosw) {
1336 report (NULL, parent->c_partno, parent->c_file,
1337 "insert text/plain part");
1338 }
1339 } else {
1340 status = NOTOK;
1341 }
1342 }
1343 } else if (parent && parent->c_type == CT_MULTIPART &&
1344 parent->c_subtype == MULTI_RELATED) {
1345 char *type_subtype =
1346 concat ((*ct)->c_ctinfo.ci_type, "/",
1347 (*ct)->c_ctinfo.ci_subtype, NULL);
1348 const char *parent_type =
1349 get_param (parent->c_ctinfo.ci_first_pm, "type", '?', 1);
1350 int new_subpart_number = 1;
1351 int has_text_plain = 0;
1352
1353 /* Have to do string comparison on the subtype because we
1354 don't enumerate all of them in c_subtype values.
1355 parent_type will be NULL if the multipart/related part
1356 doesn't have a type parameter. The type parameter must
1357 be specified according to RFC 2387 Sec. 3.1 but not all
1358 messages comply. */
1359 if (parent_type && strcasecmp (type_subtype, parent_type) == 0) {
1360 /* The type of this part matches the root type of the
1361 parent multipart/related. Look to see if there's
1362 text/plain sibling. */
1363 has_text_plain =
1364 find_textplain_sibling (parent, replacetextplain,
1365 &new_subpart_number);
1366 }
1367
1368 free (type_subtype);
1369
1370 if (! has_text_plain) {
1371 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1372 struct part *part;
1373 int siblings = 0;
1374
1375 for (part = mp->mp_parts; part; part = part->mp_next) {
1376 if (*ct != part->mp_part) {
1377 ++siblings;
1378 }
1379 }
1380
1381 if (siblings) {
1382 /* Parent is a multipart/related. Insert a new
1383 text/plain subpart in a new multipart/alternative. */
1384 if (insert_into_new_mp_alt (ct, message_mods)) {
1385 /* Not an error if text/plain couldn't be added. */
1386 }
1387 } else {
1388 /* There are no siblings, so insert a new text/plain
1389 subpart, and change the parent type from
1390 multipart/related to multipart/alternative. */
1391 const int inserted =
1392 insert_new_text_plain_part (*ct, new_subpart_number,
1393 parent);
1394
1395 if (inserted) {
1396 HF hf;
1397
1398 parent->c_subtype = MULTI_ALTERNATE;
1399 free (parent->c_ctinfo.ci_subtype);
1400 parent->c_ctinfo.ci_subtype = mh_xstrdup("alternative");
1401 if (! replace_substring (&parent->c_ctline, "/related",
1402 "/alternative")) {
1403 inform("did not find multipart/related in %s",
1404 parent->c_ctline);
1405 }
1406
1407 /* Update Content-Type header field. */
1408 for (hf = parent->c_first_hf; hf; hf = hf->next) {
1409 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1410 if (replace_substring (&hf->value, "/related",
1411 "/alternative")) {
1412 ++*message_mods;
1413 if (verbosw) {
1414 report (NULL, parent->c_partno,
1415 parent->c_file,
1416 "insert text/plain part");
1417 }
1418
1419 /* Remove, e.g., type="text/html" from
1420 multipart/alternative. */
1421 remove_parameter (hf->value, "type");
1422 break;
1423 }
1424 char *hf_value = cpytrim (hf->value);
1425 inform("did not find multipart/"
1426 "related in header %s", hf_value);
1427 free (hf_value);
1428 }
1429 }
1430 } else {
1431 /* Not an error if text/plain couldn't be inserted. */
1432 }
1433 }
1434 }
1435 } else {
1436 if (insert_into_new_mp_alt (ct, message_mods)) {
1437 status = NOTOK;
1438 }
1439 }
1440 break;
1441 }
1442
1443 case CT_MULTIPART: {
1444 struct multipart *mp = (struct multipart *) (*ct)->c_ctparams;
1445 struct part *part;
1446
1447 for (part = mp->mp_parts; status == OK && part; part = part->mp_next) {
1448 if ((*ct)->c_type == CT_MULTIPART) {
1449 status = ensure_text_plain (&part->mp_part, *ct, message_mods,
1450 replacetextplain);
1451 }
1452 }
1453 break;
1454 }
1455
1456 case CT_MESSAGE:
1457 if ((*ct)->c_subtype == MESSAGE_EXTERNAL) {
1458 struct exbody *e = (struct exbody *) (*ct)->c_ctparams;
1459
1460 status = ensure_text_plain (&e->eb_content, *ct, message_mods,
1461 replacetextplain);
1462 }
1463 break;
1464 }
1465
1466 return status;
1467 }
1468
1469
1470 /*
1471 * See if there is a sibling text/plain, and return its subpart number.
1472 */
1473 static int
1474 find_textplain_sibling (CT parent, int replacetextplain,
1475 int *new_subpart_number)
1476 {
1477 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1478 struct part *part, *prev;
1479 bool has_text_plain = false;
1480
1481 for (prev = part = mp->mp_parts; part; part = part->mp_next) {
1482 ++*new_subpart_number;
1483 if (part->mp_part->c_type == CT_TEXT &&
1484 part->mp_part->c_subtype == TEXT_PLAIN) {
1485 if (replacetextplain) {
1486 struct part *old_part;
1487 if (part == mp->mp_parts) {
1488 old_part = mp->mp_parts;
1489 mp->mp_parts = part->mp_next;
1490 } else {
1491 old_part = prev->mp_next;
1492 prev->mp_next = part->mp_next;
1493 }
1494 if (verbosw) {
1495 report (NULL, parent->c_partno, parent->c_file,
1496 "remove text/plain part %s",
1497 old_part->mp_part->c_partno);
1498 }
1499 free_content (old_part->mp_part);
1500 free (old_part);
1501 } else {
1502 has_text_plain = true;
1503 }
1504 break;
1505 }
1506 prev = part;
1507 }
1508
1509 return has_text_plain;
1510 }
1511
1512
1513 /*
1514 * Insert a new text/plain part in a multipart part.
1515 */
1516 static int
1517 insert_new_text_plain_part (CT ct, int new_subpart_number, CT parent)
1518 {
1519 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1520 struct part *new_part;
1521
1522 NEW(new_part);
1523 if ((new_part->mp_part = build_text_plain_part (ct))) {
1524 char buffer[16];
1525 snprintf (buffer, sizeof buffer, "%d", new_subpart_number);
1526
1527 new_part->mp_next = mp->mp_parts;
1528 mp->mp_parts = new_part;
1529 new_part->mp_part->c_partno =
1530 concat (parent->c_partno ? parent->c_partno : "1", ".",
1531 buffer, NULL);
1532
1533 return 1;
1534 }
1535
1536 free_content (new_part->mp_part);
1537 free (new_part);
1538
1539 return 0;
1540 }
1541
1542
1543 /*
1544 * Create a text/plain part to go along with non-plain sibling part.
1545 */
1546 static CT
1547 build_text_plain_part (CT encoded_part)
1548 {
1549 CT tp_part = divide_part (encoded_part);
1550 char *tmp_plain_file = NULL;
1551
1552 if (decode_part (tp_part) == OK) {
1553 /* Now, tp_part->c_cefile.ce_file is the name of the tmp file that
1554 contains the decoded contents. And the decoding function, such
1555 as openQuoted, will have set ...->ce_unlink to 1 so that it will
1556 be unlinked by free_content (). */
1557 char *tempfile;
1558
1559 /* This m_mktemp2() call closes the temp file. */
1560 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
1561 inform("unable to create temporary file in %s", get_temp_dir());
1562 } else {
1563 tmp_plain_file = mh_xstrdup (tempfile);
1564 if (reformat_part (tp_part, tmp_plain_file,
1565 tp_part->c_ctinfo.ci_type,
1566 tp_part->c_ctinfo.ci_subtype,
1567 tp_part->c_type) == OK) {
1568 return tp_part;
1569 }
1570 }
1571 }
1572
1573 free_content (tp_part);
1574 if (tmp_plain_file) { (void) m_unlink (tmp_plain_file); }
1575 free (tmp_plain_file);
1576
1577 return NULL;
1578 }
1579
1580
1581 /*
1582 * Slip new text/plain part into a new multipart/alternative.
1583 */
1584 static int
1585 insert_into_new_mp_alt (CT *ct, int *message_mods)
1586 {
1587 /* The following will call decode_part(). */
1588 CT tp_part = build_text_plain_part (*ct);
1589 int status = OK;
1590
1591 if (tp_part) {
1592 CT mp_alt = build_multipart (*ct, tp_part, CT_MULTIPART,
1593 MULTI_ALTERNATE);
1594 if (mp_alt) {
1595 struct multipart *mp = (struct multipart *) mp_alt->c_ctparams;
1596
1597 if (mp && mp->mp_parts) {
1598 mp->mp_parts->mp_part = tp_part;
1599 /* Make the new multipart/alternative the parent. */
1600 *ct = mp_alt;
1601
1602 ++*message_mods;
1603 if (verbosw) {
1604 report (NULL, (*ct)->c_partno, (*ct)->c_file,
1605 "insert text/plain part");
1606 }
1607 } else {
1608 free_content (tp_part);
1609 free_content (mp_alt);
1610 status = NOTOK;
1611 }
1612 } else {
1613 status = NOTOK;
1614 }
1615 } else {
1616 /* Not an error if text/plain couldn't be built. */
1617 }
1618
1619 return status;
1620 }
1621
1622
1623 /*
1624 * Slip new text/plain part into a new multipart/mixed.
1625 */
1626 static int
1627 insert_into_new_mp_mixed (CT *ct, const char *content, int *message_mods)
1628 {
1629 CT main_part = divide_part (*ct);
1630 const char *reason = NULL;
1631 const int encoding = content_encoding (main_part, &reason);
1632 int status = OK;
1633
1634 if (set_ct_type(main_part, (*ct)->c_type, (*ct)->c_subtype,
1635 main_part->c_encoding) != OK) {
1636 inform("failed to set Content-Type of main part");
1637 return NOTOK;
1638 }
1639 if (set_ct_type(*ct, (*ct)->c_type, (*ct)->c_subtype, encoding) != OK) {
1640 inform("failed to set Content-Type of new part");
1641 return NOTOK;
1642 }
1643
1644 if (main_part) {
1645 /* Load remainder into the new part. */
1646 CE cefile = &(*ct)->c_cefile;
1647 CT mp_alt;
1648
1649 cefile->ce_file =
1650 mh_xstrdup(m_mktemp2 (NULL, invo_name, NULL, &cefile->ce_fp));
1651 if (cefile->ce_file == NULL) {
1652 die("unable to create temporary file in %s", get_temp_dir());
1653 }
1654 cefile->ce_unlink = 1;
1655 fprintf (cefile->ce_fp, "%s", content);
1656
1657 /* Put both parts into a new multipart. */
1658 mp_alt = build_multipart (*ct, main_part, CT_MULTIPART, MULTI_MIXED);
1659 if (mp_alt) {
1660 struct multipart *mp = (struct multipart *) mp_alt->c_ctparams;
1661
1662 /* So fix_composite_cte doesn't try to overwrite the encoding. If
1663 the content needs to be decoded, c_encoding will be properly
1664 set. */
1665 mp_alt->c_encoding = encoding;
1666
1667 if (mp && mp->mp_parts) {
1668 mp->mp_parts->mp_part = main_part;
1669 /* Make the new multipart/alternative the parent. */
1670 *ct = mp_alt;
1671
1672 ++*message_mods;
1673 if (verbosw) {
1674 report (NULL, (*ct)->c_partno, (*ct)->c_file,
1675 "insert text/plain part");
1676 }
1677 } else {
1678 free_content (main_part);
1679 free_content (mp_alt);
1680 status = NOTOK;
1681 }
1682 } else {
1683 inform("failed to build multipart/alternate");
1684 status = NOTOK;
1685 }
1686 } else {
1687 /* Should never happen. */
1688 inform("failed to insert new text part into multipart/related");
1689 status = NOTOK;
1690 }
1691
1692 return status;
1693 }
1694
1695
1696 /*
1697 * Clone a MIME part.
1698 */
1699 static CT
1700 divide_part (CT ct)
1701 {
1702 CT new_part;
1703
1704 NEW0(new_part);
1705 /* Just copy over what is needed for decoding. c_vrsn and
1706 c_celine aren't necessary. */
1707 new_part->c_file = mh_xstrdup (ct->c_file);
1708 new_part->c_begin = ct->c_begin;
1709 new_part->c_end = ct->c_end;
1710 copy_ctinfo (&new_part->c_ctinfo, &ct->c_ctinfo);
1711 new_part->c_type = ct->c_type;
1712 new_part->c_cefile = ct->c_cefile;
1713 new_part->c_encoding = ct->c_encoding;
1714 new_part->c_ctinitfnx = ct->c_ctinitfnx;
1715 new_part->c_ceopenfnx = ct->c_ceopenfnx;
1716 new_part->c_ceclosefnx = ct->c_ceclosefnx;
1717 new_part->c_cesizefnx = ct->c_cesizefnx;
1718
1719 /* c_ctline is used by reformat__part(), so it can preserve
1720 anything after the type/subtype. */
1721 new_part->c_ctline = mh_xstrdup (ct->c_ctline);
1722
1723 return new_part;
1724 }
1725
1726
1727 /*
1728 * Copy the content info from one part to another.
1729 */
1730 static void
1731 copy_ctinfo (CI dest, CI src)
1732 {
1733 PM s_pm, d_pm;
1734
1735 dest->ci_type = src->ci_type ? mh_xstrdup (src->ci_type) : NULL;
1736 dest->ci_subtype = src->ci_subtype ? mh_xstrdup (src->ci_subtype) : NULL;
1737
1738 for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) {
1739 d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name,
1740 s_pm->pm_value, 0);
1741 if (s_pm->pm_charset) {
1742 d_pm->pm_charset = mh_xstrdup(s_pm->pm_charset);
1743 }
1744 if (s_pm->pm_lang) {
1745 d_pm->pm_lang = mh_xstrdup(s_pm->pm_lang);
1746 }
1747 }
1748
1749 dest->ci_comment = src->ci_comment ? mh_xstrdup (src->ci_comment) : NULL;
1750 dest->ci_magic = src->ci_magic ? mh_xstrdup (src->ci_magic) : NULL;
1751 }
1752
1753
1754 /*
1755 * Decode content.
1756 */
1757 static int
1758 decode_part (CT ct)
1759 {
1760 char *tempfile, *tmp_decoded;
1761 FILE *file;
1762 int status;
1763
1764 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, &file)) == NULL) {
1765 die("unable to create temporary file in %s", get_temp_dir());
1766 }
1767 tmp_decoded = mh_xstrdup (tempfile);
1768 /* The following call will load ct->c_cefile.ce_file with the tmp
1769 filename of the decoded content. tmp_decoded will contain the
1770 encoded output, get rid of that. */
1771 status = output_message_fp (ct, file, tmp_decoded);
1772 (void) m_unlink (tmp_decoded);
1773 free (tmp_decoded);
1774 if (fclose (file)) {
1775 inform("unable to close temporary file %s, continuing...", tempfile);
1776 }
1777
1778 return status;
1779 }
1780
1781
1782 /*
1783 * If base64-encoded content has a text trailer, return the location, relative
1784 * to c->c_begin, where the valid base64 ends. And return the trailer in the
1785 * addresses pointed to by remainderp. The caller is responsible for
1786 * deallocating that. If no text trailer, return ct->c_end - ct->c_begin and
1787 * leave remainderp unchanged.
1788 */
1789 static size_t
1790 get_valid_base64 (CT ct, char **remainderp) {
1791 const size_t len = ct->c_end - ct->c_begin;
1792 char *buf, format[16];
1793 size_t pos;
1794 int fd;
1795
1796 if (! ct->c_fp && ((ct->c_fp = fopen (ct->c_file, "r")) == NULL)) {
1797 advise (ct->c_file, "unable to open for reading");
1798 return NOTOK;
1799 }
1800 if ((fd = fileno (ct->c_fp)) == -1 ||
1801 lseek (fd, ct->c_begin, SEEK_SET) == (off_t) -1) {
1802 advise (ct->c_file, "unable to seek in");
1803 return NOTOK;
1804 }
1805 buf = mh_xmalloc(len + 1);
1806 snprintf(format, sizeof format, "%%%luc", (unsigned long) len);
1807 if (fscanf(ct->c_fp, format, buf) == EOF) {
1808 advise (ct->c_file, "unable to read");
1809 return NOTOK;
1810 }
1811 buf[len] = '\0';
1812
1813 pos = find_invalid_base64_pos(buf);
1814
1815 if (ct->c_begin + pos < (size_t) ct->c_end) {
1816 *remainderp = mh_xstrdup(&buf[pos]);
1817 } else {
1818 pos = ct->c_end - ct->c_begin;
1819 }
1820 free(buf);
1821
1822 return pos;
1823 }
1824
1825
1826 /*
1827 * Find position in byte string of invalid base64 code. Skip individual
1828 * invalid characters because RFC 2045 Sec 6.8 says they should be ignored.
1829 * The motivating use case is a text footer that was mistakenly applied to
1830 * base64 content. Therefore, if any of these is found, return the position
1831 * of:
1832 * 1. The byte (or end) after one or two consecutive pad ('=') bytes.
1833 * 2. The first of a pair of invalid base64 bytes.
1834 *
1835 * If the base64 code is valid, return the position of the null terminator.
1836 *
1837 * encoded - the base64-encoded string
1838 */
1839 static size_t
1840 find_invalid_base64_pos (const char *encoded) {
1841 const char *cp;
1842 size_t pos;
1843 bool found_pad = false;
1844 unsigned int found_invalid = 0;
1845
1846 for (cp = encoded, pos = 0;
1847 *cp && ! found_pad && found_invalid < 2;
1848 ++cp, ++pos) {
1849 if (isspace ((unsigned char) *cp) ||
1850 isalnum ((unsigned char) *cp) ||
1851 *cp == '+' || *cp == '/' || *cp == '=') {
1852 /* Valid base64 byte. */
1853 if (*cp == '=') {
1854 /* "evidence that the end of the data has been reached"
1855 according to RFC 2045 */
1856 found_pad = true;
1857 }
1858 /* Require consecutive invalid bytes. Let decodeBase64() handle
1859 individual ones. */
1860 found_invalid = 0;
1861 } else {
1862 ++found_invalid;
1863 }
1864 }
1865
1866 if (found_pad && *cp && *cp == '=') {
1867 /* Skip over last in pair of ==. */
1868 ++cp, ++pos;
1869 } else if (found_invalid == 2) {
1870 /* If a pair of consecutive invalid bytes, back up to first one. */
1871 --cp, --pos;
1872 --cp, --pos;
1873 }
1874
1875 /* Skip over any trailing whitespace. */
1876 while (*cp && isspace((unsigned char) *cp)) {
1877 ++cp, ++pos;
1878 }
1879
1880 return pos;
1881 }
1882
1883
1884 /*
1885 * Check for valid base64 encoding, and "fix" if invalid.
1886 */
1887 static int
1888 check_base64_encoding (CT *ctp)
1889 {
1890 char *remainder = NULL;
1891 int status = OK;
1892
1893 /* If there's a footer after base64 content, set c_end to before it, and
1894 store the footer in remainder. */
1895 (*ctp)->c_end = (*ctp)->c_begin + get_valid_base64(*ctp, &remainder);
1896
1897 if (remainder != NULL) {
1898 /* Move ct to a subpart of a new multipart/related, and add the
1899 remainder as a new text/plain subpart of it. */
1900 int ignore_message_mods = 0;
1901
1902 status = insert_into_new_mp_mixed(ctp, remainder, &ignore_message_mods);
1903 free(remainder);
1904 }
1905
1906 return status;
1907 }
1908
1909
1910 /*
1911 * Reformat content as plain text.
1912 * Some of the arguments aren't really needed now, but maybe will
1913 * be in the future for other than text types.
1914 */
1915 static int
1916 reformat_part (CT ct, char *file, char *type, char *subtype, int c_type)
1917 {
1918 int output_subtype, output_encoding;
1919 const char *reason = NULL;
1920 char *cp, *cf;
1921 int status;
1922
1923 /* Hacky: this redirects the output from whatever command is used
1924 to show the part to a file. So, the user can't have any output
1925 redirection in that command.
1926 Could show_multi() in mhshowsbr.c avoid this? */
1927
1928 /* Check for invo_name-format-type/subtype. */
1929 if ((cf = context_find_by_type ("format", type, subtype)) == NULL) {
1930 if (verbosw) {
1931 inform("Don't know how to convert %s, there is no "
1932 "%s-format-%s/%s profile entry",
1933 ct->c_file, invo_name, type, subtype);
1934 }
1935 return NOTOK;
1936 }
1937 if (strchr (cf, '>')) {
1938 inform("'>' prohibited in \"%s\",\nplease fix your "
1939 "%s-format-%s/%s profile entry", cf, invo_name, type,
1940 FENDNULL(subtype));
1941
1942 return NOTOK;
1943 }
1944
1945 cp = concat (cf, " >", file, NULL);
1946 status = show_content_aux (ct, 0, cp, NULL, NULL);
1947 free (cp);
1948
1949 /* Unlink decoded content tmp file and free its filename to avoid
1950 leaks. The file stream should already have been closed. */
1951 if (ct->c_cefile.ce_unlink) {
1952 (void) m_unlink (ct->c_cefile.ce_file);
1953 free (ct->c_cefile.ce_file);
1954 ct->c_cefile.ce_file = NULL;
1955 ct->c_cefile.ce_unlink = 0;
1956 }
1957
1958 if (c_type == CT_TEXT) {
1959 output_subtype = TEXT_PLAIN;
1960 } else {
1961 /* Set subtype to 0, which is always an UNKNOWN subtype. */
1962 output_subtype = 0;
1963 }
1964
1965 output_encoding = content_encoding (ct, &reason);
1966 if (status == OK &&
1967 set_ct_type (ct, c_type, output_subtype, output_encoding) == OK) {
1968 ct->c_cefile.ce_file = file;
1969 ct->c_cefile.ce_unlink = 1;
1970 } else {
1971 ct->c_cefile.ce_unlink = 0;
1972 status = NOTOK;
1973 }
1974
1975 return status;
1976 }
1977
1978
1979 /*
1980 * Create and fill in a multipart part.
1981 */
1982 static CT
1983 build_multipart (CT first_part, CT new_part, int type, int subtype)
1984 {
1985 char *boundary_prefix = "----=_nmh-multipart";
1986 static unsigned int bp_uses = 0;
1987 char bp_uses_buf[16];
1988 char *boundary;
1989 char *boundary_indicator = "; boundary=";
1990 char *typename, *subtypename, *name;
1991 CT ct;
1992 struct part *p;
1993 struct multipart *m;
1994 const struct str2init *ctinit;
1995
1996 NEW0(ct);
1997
1998 if (bp_uses > 0) {
1999 snprintf(bp_uses_buf, sizeof bp_uses_buf - 1, "-%d", bp_uses++);
2000 } else {
2001 bp_uses_buf[0] = '\0';
2002 }
2003 boundary =
2004 concat (boundary_prefix, bp_uses_buf, first_part->c_partno, NULL);
2005
2006 /* Set up the multipart part. These fields of *ct were
2007 initialized to 0 by mh_xcalloc():
2008 c_fp, c_unlink, c_begin, c_end,
2009 c_vrsn, c_ctline, c_celine,
2010 c_id, c_descr, c_dispo, c_partno,
2011 c_ctinfo.ci_comment, c_ctinfo.ci_magic,
2012 c_cefile, c_encoding,
2013 c_digested, c_digest[16], c_ctexbody,
2014 c_ctinitfnx, c_ceopenfnx, c_ceclosefnx, c_cesizefnx,
2015 c_umask, c_rfc934,
2016 c_showproc, c_termproc, c_storeproc, c_storage, c_folder
2017 */
2018
2019 ct->c_file = mh_xstrdup (first_part->c_file);
2020 ct->c_type = type;
2021 ct->c_subtype = subtype;
2022
2023 ctinit = get_ct_init (ct->c_type);
2024
2025 typename = ct_type_str (type);
2026 subtypename = ct_subtype_str (type, subtype);
2027
2028 {
2029 int serial = 0;
2030 int found_boundary = 1;
2031
2032 while (found_boundary && serial < 1000000) {
2033 found_boundary = 0;
2034
2035 /* Ensure that the boundary doesn't appear in the decoded
2036 content. */
2037 if (new_part->c_cefile.ce_file) {
2038 if ((found_boundary =
2039 boundary_in_content (&new_part->c_cefile.ce_fp,
2040 new_part->c_cefile.ce_file,
2041 boundary)) == NOTOK) {
2042 goto return_null;
2043 }
2044 }
2045
2046 /* Ensure that the boundary doesn't appear in the encoded
2047 content. */
2048 if (! found_boundary && new_part->c_file) {
2049 if ((found_boundary =
2050 boundary_in_content (&new_part->c_fp,
2051 new_part->c_file,
2052 boundary)) == NOTOK) {
2053 goto return_null;
2054 }
2055 }
2056
2057 if (found_boundary) {
2058 /* Try a slightly different boundary. */
2059 char buffer2[16];
2060
2061 free (boundary);
2062 ++serial;
2063 snprintf (buffer2, sizeof buffer2, "%d", serial);
2064 boundary =
2065 concat (boundary_prefix,
2066 FENDNULL(first_part->c_partno),
2067 "-", buffer2, NULL);
2068 }
2069 }
2070
2071 if (found_boundary) {
2072 inform("giving up trying to find a unique boundary");
2073 goto return_null;
2074 }
2075 }
2076
2077 name = concat (" ", typename, "/", subtypename, boundary_indicator, "\"",
2078 boundary, "\"", NULL);
2079
2080 /* Load c_first_hf and c_last_hf. */
2081 transfer_noncontent_headers (first_part, ct);
2082 add_header (ct, mh_xstrdup (TYPE_FIELD), concat (name, "\n", NULL));
2083 free (name);
2084
2085 /* Load c_partno. */
2086 if (first_part->c_partno) {
2087 ct->c_partno = mh_xstrdup (first_part->c_partno);
2088 free (first_part->c_partno);
2089 first_part->c_partno = concat (ct->c_partno, ".1", NULL);
2090 new_part->c_partno = concat (ct->c_partno, ".2", NULL);
2091 } else {
2092 first_part->c_partno = mh_xstrdup ("1");
2093 new_part->c_partno = mh_xstrdup ("2");
2094 }
2095
2096 if (ctinit) {
2097 ct->c_ctinfo.ci_type = mh_xstrdup (typename);
2098 ct->c_ctinfo.ci_subtype = mh_xstrdup (subtypename);
2099 }
2100
2101 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
2102 "boundary", boundary, 0);
2103
2104 NEW(p);
2105 NEW(p->mp_next);
2106 p->mp_next->mp_next = NULL;
2107 p->mp_next->mp_part = first_part;
2108
2109 NEW0(m);
2110 m->mp_start = concat (boundary, "\n", NULL);
2111 m->mp_stop = concat (boundary, "--\n", NULL);
2112 m->mp_parts = p;
2113 ct->c_ctparams = m;
2114
2115 free (boundary);
2116
2117 return ct;
2118
2119 return_null:
2120 free_content(ct);
2121 free(boundary);
2122 return NULL;
2123 }
2124
2125
2126 /*
2127 * Check that the boundary does not appear in the content.
2128 */
2129 static int
2130 boundary_in_content (FILE **fp, char *file, const char *boundary)
2131 {
2132 char buffer[NMH_BUFSIZ];
2133 size_t bytes_read;
2134 bool found_boundary = false;
2135
2136 /* free_content() will close *fp if we fopen it here. */
2137 if (! *fp && (*fp = fopen (file, "r")) == NULL) {
2138 advise (file, "unable to open %s for reading", file);
2139 return NOTOK;
2140 }
2141
2142 fseeko (*fp, 0L, SEEK_SET);
2143 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) > 0) {
2144 if (find_str (buffer, bytes_read, boundary)) {
2145 found_boundary = true;
2146 break;
2147 }
2148 }
2149
2150 return found_boundary;
2151 }
2152
2153
2154 /*
2155 * Remove all non-Content headers.
2156 */
2157 static void
2158 transfer_noncontent_headers (CT old, CT new)
2159 {
2160 HF hp, hp_prev;
2161
2162 hp_prev = hp = old->c_first_hf;
2163 while (hp) {
2164 HF next = hp->next;
2165
2166 if (strncasecmp (XXX_FIELD_PRF, hp->name, LEN(XXX_FIELD_PRF))) {
2167 if (hp == old->c_last_hf) {
2168 if (hp == old->c_first_hf) {
2169 old->c_last_hf = old->c_first_hf = NULL;
2170 } else {
2171 hp_prev->next = NULL;
2172 old->c_last_hf = hp_prev;
2173 }
2174 } else {
2175 if (hp == old->c_first_hf) {
2176 old->c_first_hf = next;
2177 } else {
2178 hp_prev->next = next;
2179 }
2180 }
2181
2182 /* Put node hp in the new CT. */
2183 if (new->c_first_hf == NULL) {
2184 new->c_first_hf = hp;
2185 } else {
2186 new->c_last_hf->next = hp;
2187 }
2188 new->c_last_hf = hp;
2189 } else {
2190 /* A Content- header, leave in old. */
2191 hp_prev = hp;
2192 }
2193
2194 hp = next;
2195 }
2196 }
2197
2198
2199 /*
2200 * Set content type.
2201 */
2202 static int
2203 set_ct_type (CT ct, int type, int subtype, int encoding)
2204 {
2205 char *typename = ct_type_str (type);
2206 char *subtypename = ct_subtype_str (type, subtype);
2207 /* E.g, " text/plain" */
2208 char *type_subtypename = concat (" ", typename, "/", subtypename, NULL);
2209 /* E.g, " text/plain\n" */
2210 char *name_plus_nl = concat (type_subtypename, "\n", NULL);
2211 bool found_content_type = false;
2212 HF hf;
2213 const char *cp = NULL;
2214 char *ctline;
2215 int status;
2216
2217 /* Update/add Content-Type header field. */
2218 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2219 if (! strcasecmp (TYPE_FIELD, hf->name)) {
2220 found_content_type = true;
2221 free (hf->value);
2222 hf->value = (cp = strchr (ct->c_ctline, ';'))
2223 ? concat (type_subtypename, cp, "\n", NULL)
2224 : mh_xstrdup (name_plus_nl);
2225 }
2226 }
2227 if (! found_content_type) {
2228 add_header (ct, mh_xstrdup (TYPE_FIELD),
2229 (cp = strchr (ct->c_ctline, ';'))
2230 ? concat (type_subtypename, cp, "\n", NULL)
2231 : mh_xstrdup (name_plus_nl));
2232 }
2233
2234 /* Some of these might not be used, but set them anyway. */
2235 ctline = cp
2236 ? concat (type_subtypename, cp, NULL)
2237 : concat (type_subtypename, NULL);
2238 free (ct->c_ctline);
2239 ct->c_ctline = ctline;
2240 /* Leave other ctinfo members as they were. */
2241 free (ct->c_ctinfo.ci_type);
2242 ct->c_ctinfo.ci_type = mh_xstrdup (typename);
2243 free (ct->c_ctinfo.ci_subtype);
2244 ct->c_ctinfo.ci_subtype = mh_xstrdup (subtypename);
2245 ct->c_type = type;
2246 ct->c_subtype = subtype;
2247
2248 free (name_plus_nl);
2249 free (type_subtypename);
2250
2251 status = set_ce (ct, encoding);
2252
2253 return status;
2254 }
2255
2256
2257 /*
2258 * It's not necessary to update the charset parameter of a Content-Type
2259 * header for a text part. According to RFC 2045 Sec. 6.4, the body
2260 * (content) was originally in the specified charset, "and will be in
2261 * that character set again after decoding."
2262 */
2263 static int
2264 decode_text_parts (CT ct, int encoding, const char *decodetypes,
2265 int *message_mods)
2266 {
2267 int status = OK;
2268 int lf_line_endings = 0;
2269
2270 switch (ct->c_type) {
2271 case CT_MULTIPART: {
2272 struct multipart *m = (struct multipart *) ct->c_ctparams;
2273 struct part *part;
2274
2275 /* Should check to see if the body for this part is encoded?
2276 For now, it gets passed along as-is by InitMultiPart(). */
2277 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2278 status = decode_text_parts (part->mp_part, encoding, decodetypes,
2279 message_mods);
2280 }
2281 break;
2282 }
2283
2284 case CT_MESSAGE:
2285 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2286 struct exbody *e = (struct exbody *) ct->c_ctparams;
2287
2288 status = decode_text_parts (e->eb_content, encoding, decodetypes,
2289 message_mods);
2290 }
2291 break;
2292
2293 default:
2294 if (! should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
2295 break;
2296 }
2297
2298 lf_line_endings =
2299 ct->c_ctparams && ((struct text *) ct->c_ctparams)->lf_line_endings;
2300
2301 switch (ct->c_encoding) {
2302 case CE_BASE64:
2303 case CE_QUOTED: {
2304 int ct_encoding;
2305
2306 if (decode_part (ct) == OK && ct->c_cefile.ce_file) {
2307 const char *reason = NULL;
2308
2309 if ((ct_encoding = content_encoding (ct, &reason)) == CE_BINARY
2310 && encoding != CE_BINARY) {
2311 /* The decoding isn't acceptable so discard it.
2312 Leave status as OK to allow other transformations. */
2313 if (verbosw) {
2314 report (NULL, ct->c_partno, ct->c_file,
2315 "will not decode%s because it is binary (%s)",
2316 ct->c_partno ? ""
2317 : (FENDNULL(ct->c_ctline)),
2318 reason);
2319 }
2320 (void) m_unlink (ct->c_cefile.ce_file);
2321 free (ct->c_cefile.ce_file);
2322 ct->c_cefile.ce_file = NULL;
2323 } else if (ct->c_encoding == CE_QUOTED &&
2324 ct_encoding == CE_8BIT && encoding == CE_7BIT) {
2325 /* The decoding isn't acceptable so discard it.
2326 Leave status as OK to allow other transformations. */
2327 if (verbosw) {
2328 report (NULL, ct->c_partno, ct->c_file,
2329 "will not decode%s because it is 8bit",
2330 ct->c_partno ? ""
2331 : (FENDNULL(ct->c_ctline)));
2332 }
2333 (void) m_unlink (ct->c_cefile.ce_file);
2334 free (ct->c_cefile.ce_file);
2335 ct->c_cefile.ce_file = NULL;
2336 } else {
2337 int enc;
2338
2339 if (ct_encoding == CE_BINARY) {
2340 enc = CE_BINARY;
2341 } else if (ct_encoding == CE_8BIT && encoding == CE_7BIT) {
2342 enc = CE_QUOTED;
2343 } else {
2344 enc = ct_encoding;
2345 }
2346 if (set_ce (ct, enc) == OK) {
2347 ++*message_mods;
2348 if (verbosw) {
2349 report (NULL, ct->c_partno, ct->c_file, "decode%s",
2350 FENDNULL(ct->c_ctline));
2351 }
2352 if (lf_line_endings) {
2353 strip_crs (ct, message_mods);
2354 }
2355 } else {
2356 status = NOTOK;
2357 }
2358 }
2359 } else {
2360 status = NOTOK;
2361 }
2362 break;
2363 }
2364 case CE_8BIT:
2365 case CE_7BIT:
2366 if (lf_line_endings) {
2367 strip_crs (ct, message_mods);
2368 }
2369 break;
2370 default:
2371 break;
2372 }
2373
2374 break;
2375 }
2376
2377 return status;
2378 }
2379
2380
2381 /*
2382 * Determine if the part with type[/subtype] should be decoded, according to
2383 * decodetypes (which came from the -decodetypes switch).
2384 */
2385 static int
2386 should_decode(const char *decodetypes, const char *type, const char *subtype)
2387 {
2388 /* Quick search for matching type[/subtype] in decodetypes: bracket
2389 decodetypes with commas, then search for ,type, and ,type/subtype, in
2390 it. */
2391
2392 bool found_match = false;
2393 char *delimited_decodetypes = concat(",", decodetypes, ",", NULL);
2394 char *delimited_type = concat(",", type, ",", NULL);
2395
2396 if (nmh_strcasestr(delimited_decodetypes, delimited_type)) {
2397 found_match = true;
2398 } else if (subtype != NULL) {
2399 char *delimited_type_subtype =
2400 concat(",", type, "/", subtype, ",", NULL);
2401
2402 if (nmh_strcasestr(delimited_decodetypes, delimited_type_subtype)) {
2403 found_match = true;
2404 }
2405 free(delimited_type_subtype);
2406 }
2407
2408 free(delimited_type);
2409 free(delimited_decodetypes);
2410
2411 return found_match;
2412 }
2413
2414
2415 /*
2416 * See if the decoded content is 7bit, 8bit, or binary. It's binary
2417 * if it has any NUL characters, a CR not followed by a LF, or lines
2418 * greater than 998 characters in length. If binary, reason is set
2419 * to a string explaining why.
2420 */
2421 static int
2422 content_encoding (CT ct, const char **reason)
2423 {
2424 CE ce = &ct->c_cefile;
2425 int encoding = CE_7BIT;
2426
2427 if (ce->ce_file) {
2428 size_t line_len = 0;
2429 char buffer[NMH_BUFSIZ];
2430 size_t inbytes;
2431
2432 if (! ce->ce_fp && (ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2433 advise (ce->ce_file, "unable to open for reading");
2434 return CE_UNKNOWN;
2435 }
2436
2437 fseeko (ce->ce_fp, 0L, SEEK_SET);
2438 while (encoding != CE_BINARY &&
2439 (inbytes = fread (buffer, 1, sizeof buffer, ce->ce_fp)) > 0) {
2440 char *cp;
2441 size_t i;
2442 int last_char_was_cr = 0;
2443
2444 for (i = 0, cp = buffer; i < inbytes; ++i, ++cp) {
2445 if (*cp == '\0' || ++line_len > 998 ||
2446 (*cp != '\n' && last_char_was_cr)) {
2447 encoding = CE_BINARY;
2448 if (*cp == '\0') {
2449 *reason = "null character";
2450 } else if (line_len > 998) {
2451 *reason = "line length > 998";
2452 } else if (*cp != '\n' && last_char_was_cr) {
2453 *reason = "CR not followed by LF";
2454 } else {
2455 /* Should not reach this. */
2456 *reason = "";
2457 }
2458 break;
2459 }
2460 if (*cp == '\n') {
2461 line_len = 0;
2462 } else if (! isascii ((unsigned char) *cp)) {
2463 encoding = CE_8BIT;
2464 }
2465
2466 last_char_was_cr = *cp == '\r';
2467 }
2468 }
2469
2470 fclose (ce->ce_fp);
2471 ce->ce_fp = NULL;
2472 } /* else should never happen */
2473
2474 return encoding;
2475 }
2476
2477
2478 /*
2479 * Strip carriage returns from content.
2480 */
2481 static int
2482 strip_crs (CT ct, int *message_mods)
2483 {
2484 char *charset = content_charset (ct);
2485 int status = OK;
2486
2487 /* Only strip carriage returns if content is ASCII or another
2488 charset that has the same readily recognizable CR followed by a
2489 LF. We can include UTF-8 here because if the high-order bit of
2490 a UTF-8 byte is 0, then it must be a single-byte ASCII
2491 character. */
2492 if (! strcasecmp (charset, "US-ASCII") ||
2493 ! strcasecmp (charset, "UTF-8") ||
2494 ! strncasecmp (charset, "ISO-8859-", 9) ||
2495 ! strncasecmp (charset, "WINDOWS-12", 10)) {
2496 char **file = NULL;
2497 FILE **fp = NULL;
2498 size_t begin;
2499 size_t end;
2500 bool has_crs = false;
2501 bool opened_input_file = false;
2502
2503 if (ct->c_cefile.ce_file) {
2504 file = &ct->c_cefile.ce_file;
2505 fp = &ct->c_cefile.ce_fp;
2506 begin = end = 0;
2507 } else if (ct->c_file) {
2508 file = &ct->c_file;
2509 fp = &ct->c_fp;
2510 begin = (size_t) ct->c_begin;
2511 end = (size_t) ct->c_end;
2512 } /* else don't know where the content is */
2513
2514 if (file && *file && fp) {
2515 if (! *fp) {
2516 if ((*fp = fopen (*file, "r")) == NULL) {
2517 advise (*file, "unable to open for reading");
2518 status = NOTOK;
2519 } else {
2520 opened_input_file = true;
2521 }
2522 }
2523 }
2524
2525 if (fp && *fp) {
2526 char buffer[NMH_BUFSIZ];
2527 size_t bytes_read;
2528 size_t bytes_to_read =
2529 end > 0 && end > begin ? end - begin : sizeof buffer;
2530
2531 fseeko (*fp, begin, SEEK_SET);
2532 while ((bytes_read = fread (buffer, 1,
2533 min (bytes_to_read, sizeof buffer),
2534 *fp)) > 0) {
2535 /* Look for CR followed by a LF. This is supposed to
2536 be text so there should be LF's. If not, don't
2537 modify the content. */
2538 char *cp;
2539 size_t i;
2540 bool last_char_was_cr = false;
2541
2542 if (end > 0) { bytes_to_read -= bytes_read; }
2543
2544 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
2545 if (*cp == '\n' && last_char_was_cr) {
2546 has_crs = true;
2547 break;
2548 }
2549
2550 last_char_was_cr = *cp == '\r';
2551 }
2552 }
2553
2554 if (has_crs) {
2555 int fd;
2556 char *stripped_content_file;
2557 char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL);
2558
2559 if (tempfile == NULL) {
2560 die("unable to create temporary file in %s",
2561 get_temp_dir());
2562 }
2563 stripped_content_file = mh_xstrdup (tempfile);
2564
2565 /* Strip each CR before a LF from the content. */
2566 fseeko (*fp, begin, SEEK_SET);
2567 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) >
2568 0) {
2569 char *cp;
2570 size_t i;
2571 bool last_char_was_cr = false;
2572
2573 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
2574 if (*cp == '\r') {
2575 last_char_was_cr = true;
2576 } else if (last_char_was_cr) {
2577 if (*cp != '\n') {
2578 if (write (fd, "\r", 1) < 0) {
2579 advise (tempfile, "CR write");
2580 }
2581 }
2582 if (write (fd, cp, 1) < 0) {
2583 advise (tempfile, "write");
2584 }
2585 last_char_was_cr = false;
2586 } else {
2587 if (write (fd, cp, 1) < 0) {
2588 advise (tempfile, "write");
2589 }
2590 last_char_was_cr = false;
2591 }
2592 }
2593 }
2594
2595 if (close (fd)) {
2596 inform("unable to write temporary file %s, continuing...",
2597 stripped_content_file);
2598 (void) m_unlink (stripped_content_file);
2599 free(stripped_content_file);
2600 status = NOTOK;
2601 } else {
2602 /* Replace the decoded file with the converted one. */
2603 if (ct->c_cefile.ce_file && ct->c_cefile.ce_unlink)
2604 (void) m_unlink (ct->c_cefile.ce_file);
2605
2606 free(ct->c_cefile.ce_file);
2607 ct->c_cefile.ce_file = stripped_content_file;
2608 ct->c_cefile.ce_unlink = 1;
2609
2610 ++*message_mods;
2611 if (verbosw) {
2612 report (NULL, ct->c_partno,
2613 begin == 0 && end == 0 ? "" : *file,
2614 "stripped CRs");
2615 }
2616 }
2617 }
2618
2619 if (opened_input_file) {
2620 fclose (*fp);
2621 *fp = NULL;
2622 }
2623 }
2624 }
2625
2626 free (charset);
2627
2628 return status;
2629 }
2630
2631
2632 /*
2633 * Add/update, if necessary, the message C-T-E, based on the least restrictive
2634 * of the part C-T-E's.
2635 */
2636 static void
2637 update_cte (CT ct)
2638 {
2639 const int least_restrictive_enc = least_restrictive_encoding (ct);
2640
2641 if (least_restrictive_enc != CE_UNKNOWN &&
2642 least_restrictive_enc != CE_7BIT) {
2643 char *cte = concat (" ", ce_str (least_restrictive_enc), "\n", NULL);
2644 HF hf;
2645 bool found_cte = false;
2646
2647 /* Update/add Content-Transfer-Encoding header field. */
2648 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2649 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
2650 found_cte = true;
2651 free (hf->value);
2652 hf->value = cte;
2653 }
2654 }
2655 if (! found_cte) {
2656 add_header (ct, mh_xstrdup (ENCODING_FIELD), cte);
2657 }
2658 }
2659 }
2660
2661
2662 /*
2663 * Find the least restrictive encoding (7bit, 8bit, binary) of the parts
2664 * within a message.
2665 */
2666 static int
2667 least_restrictive_encoding (CT ct)
2668 {
2669 int encoding = CE_UNKNOWN;
2670
2671 switch (ct->c_type) {
2672 case CT_MULTIPART: {
2673 struct multipart *m = (struct multipart *) ct->c_ctparams;
2674 struct part *part;
2675
2676 for (part = m->mp_parts; part; part = part->mp_next) {
2677 const int part_encoding =
2678 least_restrictive_encoding (part->mp_part);
2679
2680 if (less_restrictive (encoding, part_encoding)) {
2681 encoding = part_encoding;
2682 }
2683 }
2684 break;
2685 }
2686
2687 case CT_MESSAGE:
2688 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2689 struct exbody *e = (struct exbody *) ct->c_ctparams;
2690 const int part_encoding =
2691 least_restrictive_encoding (e->eb_content);
2692
2693 if (less_restrictive (encoding, part_encoding)) {
2694 encoding = part_encoding;
2695 }
2696 }
2697 break;
2698
2699 default: {
2700 if (less_restrictive (encoding, ct->c_encoding)) {
2701 encoding = ct->c_encoding;
2702 }
2703 }}
2704
2705 return encoding;
2706 }
2707
2708
2709 /*
2710 * Return whether the second encoding is less restrictive than the first, where
2711 * "less restrictive" is in the sense used by RFC 2045 Secs. 6.1 and 6.4. So,
2712 * CE_BINARY is less restrictive than CE_8BIT and
2713 * CE_8BIT is less restrictive than CE_7BIT.
2714 */
2715 static int
2716 less_restrictive (int encoding, int second_encoding)
2717 {
2718 switch (second_encoding) {
2719 case CE_BINARY:
2720 return encoding != CE_BINARY;
2721 case CE_8BIT:
2722 return encoding != CE_BINARY && encoding != CE_8BIT;
2723 case CE_7BIT:
2724 return encoding != CE_BINARY && encoding != CE_8BIT &&
2725 encoding != CE_7BIT;
2726 default :
2727 return 0;
2728 }
2729 }
2730
2731
2732 /*
2733 * Convert character set of each part.
2734 */
2735 static int
2736 convert_charsets (CT ct, char *dest_charset, int *message_mods)
2737 {
2738 int status = OK;
2739
2740 switch (ct->c_type) {
2741 case CT_TEXT:
2742 if (ct->c_subtype == TEXT_PLAIN) {
2743 status = convert_charset (ct, dest_charset, message_mods);
2744 if (status == OK) {
2745 if (verbosw) {
2746 char *ct_charset = content_charset (ct);
2747
2748 report (NULL, ct->c_partno, ct->c_file,
2749 "convert %s to %s", ct_charset, dest_charset);
2750 free (ct_charset);
2751 }
2752 } else {
2753 char *ct_charset = content_charset (ct);
2754
2755 report ("iconv", ct->c_partno, ct->c_file,
2756 "failed to convert %s to %s", ct_charset, dest_charset);
2757 free (ct_charset);
2758 }
2759 }
2760 break;
2761
2762 case CT_MULTIPART: {
2763 struct multipart *m = (struct multipart *) ct->c_ctparams;
2764 struct part *part;
2765
2766 /* Should check to see if the body for this part is encoded?
2767 For now, it gets passed along as-is by InitMultiPart(). */
2768 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2769 status =
2770 convert_charsets (part->mp_part, dest_charset, message_mods);
2771 }
2772 break;
2773 }
2774
2775 case CT_MESSAGE:
2776 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2777 struct exbody *e = (struct exbody *) ct->c_ctparams;
2778
2779 status =
2780 convert_charsets (e->eb_content, dest_charset, message_mods);
2781 }
2782 break;
2783
2784 default:
2785 break;
2786 }
2787
2788 return status;
2789 }
2790
2791
2792 /*
2793 * Fix various problems that aren't handled elsewhere. These
2794 * are fixed unconditionally: there are no switches to disable
2795 * them. Currently, "problems" are these:
2796 * 1) remove extraneous semicolon at the end of a header parameter list
2797 * 2) replace RFC 2047 encoding with RFC 2231 encoding of name and
2798 * filename parameters in Content-Type and Content-Disposition
2799 * headers, respectively.
2800 */
2801 static int
2802 fix_always (CT *ctp, const fix_transformations *fx, int *message_mods)
2803 {
2804 int status = OK;
2805
2806 switch ((*ctp)->c_type) {
2807 case CT_MULTIPART: {
2808 struct multipart *m = (struct multipart *) (*ctp)->c_ctparams;
2809 struct part *part;
2810
2811 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2812 status = fix_always (&part->mp_part, fx, message_mods);
2813 }
2814 break;
2815 }
2816
2817 case CT_MESSAGE:
2818 if ((*ctp)->c_subtype == MESSAGE_EXTERNAL) {
2819 struct exbody *e = (struct exbody *) (*ctp)->c_ctparams;
2820
2821 status = fix_always (&e->eb_content, fx, message_mods);
2822 }
2823 break;
2824
2825 default: {
2826 HF hf;
2827
2828 if ((*ctp)->c_first_hf) {
2829 fix_filename_encoding (*ctp);
2830 }
2831
2832 for (hf = (*ctp)->c_first_hf; hf; hf = hf->next) {
2833 size_t len = strlen (hf->value);
2834
2835 if (strcasecmp (hf->name, TYPE_FIELD) != 0 &&
2836 strcasecmp (hf->name, DISPO_FIELD) != 0) {
2837 /* Only do this for Content-Type and
2838 Content-Disposition fields because those are the
2839 only headers that parse_mime() warns about. */
2840 continue;
2841 }
2842
2843 /* whitespace following a trailing ';' will be nuked as well */
2844 if (hf->value[len - 1] == '\n') {
2845 while (isspace((unsigned char)(hf->value[len - 2]))) {
2846 if (len-- == 0) { break; }
2847 }
2848 }
2849
2850 if (hf->value[len - 2] == ';') {
2851 /* Remove trailing ';' from parameter value. */
2852 hf->value[len - 2] = '\n';
2853 hf->value[len - 1] = '\0';
2854
2855 /* Also, if Content-Type parameter, remove trailing ';'
2856 from (*ctp)->c_ctline. This probably isn't necessary
2857 but can't hurt. */
2858 if (strcasecmp(hf->name, TYPE_FIELD) == 0 && (*ctp)->c_ctline) {
2859 size_t l = strlen((*ctp)->c_ctline) - 1;
2860 while (isspace((unsigned char)((*ctp)->c_ctline[l])) ||
2861 (*ctp)->c_ctline[l] == ';') {
2862 (*ctp)->c_ctline[l--] = '\0';
2863 if (l == 0) { break; }
2864 }
2865 }
2866
2867 ++*message_mods;
2868 if (verbosw) {
2869 report (NULL, (*ctp)->c_partno, (*ctp)->c_file,
2870 "remove trailing ; from %s parameter value",
2871 hf->name);
2872 }
2873 }
2874 }
2875
2876 if (fx->checkbase64 && (*ctp)->c_encoding == CE_BASE64) {
2877 status = check_base64_encoding (ctp);
2878 }
2879 }}
2880
2881 return status;
2882 }
2883
2884
2885 /*
2886 * Decodes UTF-8 encoded header values. Similar to fix_filename_param(), but
2887 * does not modify any MIME parameter values.
2888 */
2889 static int
2890 decode_header_field_bodies (CT ct, int *message_mods)
2891 {
2892 int status = OK;
2893
2894 switch (ct->c_type) {
2895 case CT_MULTIPART: {
2896 struct multipart *m = (struct multipart *) ct->c_ctparams;
2897 struct part *part;
2898
2899 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2900 status = decode_header_field_bodies (part->mp_part, message_mods);
2901 }
2902 break;
2903 }
2904
2905 case CT_MESSAGE:
2906 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2907 struct exbody *e = (struct exbody *) ct->c_ctparams;
2908
2909 status = decode_header_field_bodies (e->eb_content, message_mods);
2910 }
2911 break;
2912 }
2913
2914 HF hf;
2915
2916 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2917 /* Only decode UTF-8 values. */
2918 if (hf->value && has_suffix(hf->value, "?=\n") &&
2919 (! strncasecmp (hf->value, " =?utf8?", 8) ||
2920 ! strncasecmp (hf->value, " =?utf-8?", 9))) {
2921 /* Looks like an RFC 2047 encoded parameter. */
2922 char decoded[PATH_MAX + 1];
2923
2924 if (decode_rfc2047 (hf->value, decoded, sizeof decoded)) {
2925 const size_t len = strlen(decoded);
2926
2927 /* decode_rfc2047() could truncate if the buffer fills up.
2928 Detect and discard if that happened. */
2929 if (len < sizeof(decoded) - 1 && strcmp(hf->value, decoded)) {
2930 hf->value = mh_xrealloc (hf->value, len + 1);
2931 strncpy (hf->value, decoded, len + 1);
2932 ++*message_mods;
2933 }
2934 } else {
2935 char *hf_value = cpytrim (hf->value);
2936 inform("failed to decode %s parameter %s", hf->name, hf_value);
2937 free (hf_value);
2938 status = NOTOK;
2939 }
2940 }
2941 }
2942
2943 return status;
2944 }
2945
2946
2947 /*
2948 * Factor out common code for loops in fix_filename_encoding().
2949 */
2950 static int
2951 fix_filename_param (char *name, char *value, PM *first_pm, PM *last_pm)
2952 {
2953 bool fixed = false;
2954
2955 if (has_prefix(value, "=?") && has_suffix(value, "?=")) {
2956 /* Looks like an RFC 2047 encoded parameter. */
2957 char decoded[PATH_MAX + 1];
2958
2959 if (decode_rfc2047 (value, decoded, sizeof decoded)) {
2960 /* Encode using RFC 2231. */
2961 replace_param (first_pm, last_pm, name, decoded, 0);
2962 fixed = true;
2963 } else {
2964 inform("failed to decode %s parameter %s", name, value);
2965 }
2966 }
2967
2968 return fixed;
2969 }
2970
2971
2972 /*
2973 * Replace RFC 2047 encoding with RFC 2231 encoding of name and
2974 * filename parameters in Content-Type and Content-Disposition
2975 * headers, respectively.
2976 */
2977 static int
2978 fix_filename_encoding (CT ct)
2979 {
2980 PM pm;
2981 HF hf;
2982 int fixed = 0;
2983
2984 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
2985 if (pm->pm_name && pm->pm_value &&
2986 strcasecmp (pm->pm_name, "name") == 0) {
2987 fixed = fix_filename_param (pm->pm_name, pm->pm_value,
2988 &ct->c_ctinfo.ci_first_pm,
2989 &ct->c_ctinfo.ci_last_pm);
2990 }
2991 }
2992
2993 for (pm = ct->c_dispo_first; pm; pm = pm->pm_next) {
2994 if (pm->pm_name && pm->pm_value &&
2995 strcasecmp (pm->pm_name, "filename") == 0) {
2996 fixed = fix_filename_param (pm->pm_name, pm->pm_value,
2997 &ct->c_dispo_first,
2998 &ct->c_dispo_last);
2999 }
3000 }
3001
3002 /* Fix hf values to correspond. */
3003 for (hf = ct->c_first_hf; fixed && hf; hf = hf->next) {
3004 enum { OTHER, TYPE_HEADER, DISPO_HEADER } field = OTHER;
3005
3006 if (strcasecmp (hf->name, TYPE_FIELD) == 0) {
3007 field = TYPE_HEADER;
3008 } else if (strcasecmp (hf->name, DISPO_FIELD) == 0) {
3009 field = DISPO_HEADER;
3010 }
3011
3012 if (field != OTHER) {
3013 const char *const semicolon_loc = strchr (hf->value, ';');
3014
3015 if (semicolon_loc) {
3016 const size_t len =
3017 strlen (hf->name) + 1 + semicolon_loc - hf->value;
3018 const char *const params =
3019 output_params (len,
3020 field == TYPE_HEADER
3021 ? ct->c_ctinfo.ci_first_pm
3022 : ct->c_dispo_first,
3023 NULL, 0);
3024 const char *const new_params = concat (params, "\n", NULL);
3025
3026 replace_substring (&hf->value, semicolon_loc, new_params);
3027 free((void *)new_params); /* Cast away const. Sigh. */
3028 free((void *)params);
3029 }
3030 }
3031 }
3032
3033 return OK;
3034 }
3035
3036
3037 /*
3038 * Output content in input file to output file.
3039 */
3040 static int
3041 write_content (CT ct, const char *input_filename, char *outfile, FILE *outfp,
3042 int modify_inplace, int message_mods)
3043 {
3044 int status = OK;
3045
3046 if (modify_inplace) {
3047 if (message_mods > 0) {
3048 if ((status = output_message_fp (ct, outfp, outfile)) == OK) {
3049 char *infile = input_filename
3050 ? mh_xstrdup (input_filename)
3051 : mh_xstrdup (ct->c_file ? ct->c_file : "-");
3052
3053 if (remove_file (infile) == OK) {
3054 if (rename (outfile, infile)) {
3055 /* Rename didn't work, possibly because of an
3056 attempt to rename across filesystems. Try
3057 brute force copy. */
3058 int old = open (outfile, O_RDONLY);
3059 int new =
3060 open (infile, O_WRONLY | O_CREAT, m_gmprot ());
3061 int i = -1;
3062
3063 if (old != -1 && new != -1) {
3064 char buffer[NMH_BUFSIZ];
3065
3066 while ((i = read (old, buffer, sizeof buffer)) >
3067 0) {
3068 if (write (new, buffer, i) != i) {
3069 i = -1;
3070 break;
3071 }
3072 }
3073 }
3074 if (new != -1) { close (new); }
3075 if (old != -1) { close (old); }
3076 (void) m_unlink (outfile);
3077
3078 if (i < 0) {
3079 /* The -file argument processing used path() to
3080 expand filename to absolute path. */
3081 int file = ct->c_file && ct->c_file[0] == '/';
3082
3083 inform("unable to rename %s %s to %s, continuing...",
3084 file ? "file" : "message", outfile,
3085 infile);
3086 status = NOTOK;
3087 }
3088 }
3089 } else {
3090 inform("unable to remove input file %s, "
3091 "not modifying it, continuing...", infile);
3092 (void) m_unlink (outfile);
3093 status = NOTOK;
3094 }
3095
3096 free (infile);
3097 } else {
3098 status = NOTOK;
3099 }
3100 } else {
3101 /* No modifications and didn't need the tmp outfile. */
3102 (void) m_unlink (outfile);
3103 }
3104 } else {
3105 /* Output is going to some file. Produce it whether or not
3106 there were modifications. */
3107 status = output_message_fp (ct, outfp, outfile);
3108 }
3109
3110 flush_errors ();
3111 return status;
3112 }
3113
3114
3115 /*
3116 * parse_mime() does not set lf_line_endings in struct text, so use this
3117 * function to do it. It touches the parts the decodetypes identifies.
3118 */
3119 static void
3120 set_text_ctparams(CT ct, char *decodetypes, int lf_line_endings)
3121 {
3122 switch (ct->c_type) {
3123 case CT_MULTIPART: {
3124 struct multipart *m = (struct multipart *) ct->c_ctparams;
3125 struct part *part;
3126
3127 for (part = m->mp_parts; part; part = part->mp_next) {
3128 set_text_ctparams(part->mp_part, decodetypes, lf_line_endings);
3129 }
3130 break;
3131 }
3132
3133 case CT_MESSAGE:
3134 if (ct->c_subtype == MESSAGE_EXTERNAL) {
3135 struct exbody *e = (struct exbody *) ct->c_ctparams;
3136
3137 set_text_ctparams(e->eb_content, decodetypes, lf_line_endings);
3138 }
3139 break;
3140
3141 default:
3142 if (should_decode(decodetypes, ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype)) {
3143 if (ct->c_ctparams == NULL) {
3144 ct->c_ctparams = mh_xcalloc(1, sizeof (struct text));
3145 }
3146 ((struct text *) ct->c_ctparams)->lf_line_endings = lf_line_endings;
3147 }
3148 }
3149 }
3150
3151
3152 /*
3153 * If "rmmproc" is defined, call that to remove the file. Otherwise,
3154 * use the standard MH backup file.
3155 */
3156 static int
3157 remove_file (const char *file)
3158 {
3159 if (rmmproc) {
3160 char *rmm_command = concat (rmmproc, " ", file, NULL);
3161 int status = system (rmm_command);
3162
3163 free (rmm_command);
3164 return WIFEXITED (status) ? WEXITSTATUS (status) : NOTOK;
3165 }
3166 /* This is OK for a non-message file, it still uses the
3167 BACKUP_PREFIX form. The backup file will be in the same
3168 directory as file. */
3169 return rename (file, m_backup (file));
3170 }
3171
3172
3173 /*
3174 * Output formatted message to user.
3175 */
3176 static void
3177 report (char *what, char *partno, char *filename, char *message, ...)
3178 {
3179 va_list args;
3180 char *fmt;
3181
3182 if (verbosw) {
3183 va_start (args, message);
3184 fmt = concat (filename, partno ? " part " : ", ",
3185 FENDNULL(partno), partno ? ", " : "", message, NULL);
3186
3187 advertise (what, NULL, fmt, args);
3188
3189 free (fmt);
3190 va_end (args);
3191 }
3192 }
3193
3194
3195 static void
3196 pipeser (int i)
3197 {
3198 if (i == SIGQUIT) {
3199 fflush (stdout);
3200 fprintf (stderr, "\n");
3201 fflush (stderr);
3202 }
3203
3204 done (1);
3205 /* NOTREACHED */
3206 }