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