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