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