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