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