]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
9730e60b2c9d0281e60303903ebd3b189fe478ab
[nmh] / uip / mhbuildsbr.c
1 /* mhbuildsbr.c -- routines to expand/translate MIME composition files
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 /*
9 * This code was originally part of mhn.c. I split it into
10 * a separate program (mhbuild.c) and then later split it
11 * again (mhbuildsbr.c). But the code still has some of
12 * the mhn.c code in it. This program needs additional
13 * streamlining and removal of unneeded code.
14 */
15
16 #include "h/mh.h"
17 #include "sbr/m_convert.h"
18 #include "sbr/getfolder.h"
19 #include "sbr/folder_read.h"
20 #include "sbr/folder_free.h"
21 #include "sbr/context_find.h"
22 #include "sbr/brkstring.h"
23 #include "sbr/pidstatus.h"
24 #include "sbr/path.h"
25 #include "sbr/error.h"
26 #include <fcntl.h>
27 #include "h/md5.h"
28 #include "h/mts.h"
29 #include "h/tws.h"
30 #include "h/fmt_scan.h"
31 #include "h/mime.h"
32 #include "h/mhparse.h"
33 #include "h/done.h"
34 #include "h/utils.h"
35 #include "h/mhcachesbr.h"
36 #include "mhmisc.h"
37 #include "sbr/m_mktemp.h"
38 #include "sbr/message_id.h"
39 #include "sbr/mime_type.h"
40 #include "mhfree.h"
41 #include "mhshowsbr.h"
42
43 #ifdef HAVE_SYS_TIME_H
44 # include <sys/time.h>
45 #endif
46 #include <time.h>
47
48
49 extern int debugsw;
50
51 extern bool listsw;
52 extern bool rfc934sw;
53 extern bool contentidsw;
54
55 static char prefix[] = "----- =_aaaaaaaaaa";
56
57 struct attach_list {
58 char *filename;
59 struct attach_list *next;
60 };
61
62 typedef struct convert_list {
63 char *type;
64 char *filename;
65 char *argstring;
66 struct convert_list *next;
67 } convert_list;
68
69
70 /*
71 * static prototypes
72 */
73 static int init_decoded_content (CT, const char *);
74 static void setup_attach_content(CT, char *);
75 static void set_disposition (CT);
76 static void set_charset (CT, int);
77 static void expand_pseudoheaders (CT, struct multipart *, const char *,
78 const convert_list *);
79 static void expand_pseudoheader (CT, CT *, struct multipart *, const char *,
80 const char *, const char *);
81 static char *fgetstr (char *, int, FILE *);
82 static int user_content (FILE *, char *, CT *, const char *infilename);
83 static void set_id (CT, int);
84 static int compose_content (CT, int);
85 static int scan_content (CT, size_t);
86 static int build_headers (CT, int);
87 static char *calculate_digest (CT, int);
88 static int extract_headers (CT, char *, FILE **);
89
90
91 static unsigned char directives_stack[32];
92 static unsigned int directives_index;
93
94 static int
95 do_direct(void)
96 {
97 return directives_stack[directives_index];
98 }
99
100 static void
101 directive_onoff(int onoff)
102 {
103 if (directives_index >= sizeof(directives_stack) - 1) {
104 fprintf(stderr, "mhbuild: #on/off overflow, continuing\n");
105 return;
106 }
107 directives_stack[++directives_index] = onoff;
108 }
109
110 static void
111 directive_init(int onoff)
112 {
113 directives_index = 0;
114 directives_stack[0] = onoff;
115 }
116
117 static void
118 directive_pop(void)
119 {
120 if (directives_index > 0)
121 directives_index--;
122 else
123 fprintf(stderr, "mhbuild: #pop underflow, continuing\n");
124 }
125
126 /*
127 * Main routine for translating composition file
128 * into valid MIME message. It translates the draft
129 * into a content structure (actually a tree of content
130 * structures). This message then can be manipulated
131 * in various ways, including being output via
132 * output_message().
133 */
134
135 CT
136 build_mime (char *infile, int autobuild, int dist, int directives,
137 int header_encoding, size_t maxunencoded, int verbose)
138 {
139 int compnum, state;
140 char buf[NMH_BUFSIZ], name[NAMESZ];
141 char *cp, *np, *vp;
142 struct multipart *m;
143 struct part **pp;
144 CT ct;
145 FILE *in;
146 HF hp;
147 m_getfld_state_t gstate;
148 struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
149 convert_list *convert_head = NULL, *convert_tail = NULL, *convert;
150
151 directive_init(directives);
152
153 umask (~m_gmprot ());
154
155 /* open the composition draft */
156 if ((in = fopen (infile, "r")) == NULL)
157 adios (infile, "unable to open for reading");
158
159 /*
160 * Allocate space for primary (outside) content
161 */
162 NEW0(ct);
163
164 /*
165 * Allocate structure for handling decoded content
166 * for this part. We don't really need this, but
167 * allocate it to remain consistent.
168 */
169 init_decoded_content (ct, infile);
170
171 /*
172 * Parse some of the header fields in the composition
173 * draft into the linked list of header fields for
174 * the new MIME message.
175 */
176 gstate = m_getfld_state_init(in);
177 m_getfld_track_filepos2(&gstate);
178 for (compnum = 1;;) {
179 int bufsz = sizeof buf;
180 switch (state = m_getfld2(&gstate, name, buf, &bufsz)) {
181 case FLD:
182 case FLDPLUS:
183 compnum++;
184
185 /* abort if draft has Mime-Version or C-T-E header field */
186 if (strcasecmp (name, VRSN_FIELD) == 0 ||
187 strcasecmp (name, ENCODING_FIELD) == 0) {
188 if (autobuild) {
189 fclose(in);
190 free (ct);
191 return NULL;
192 }
193 die("draft shouldn't contain %s: field", name);
194 }
195
196 /* ignore any Content-Type fields in the header */
197 if (!strcasecmp (name, TYPE_FIELD)) {
198 while (state == FLDPLUS) {
199 bufsz = sizeof buf;
200 state = m_getfld2(&gstate, name, buf, &bufsz);
201 }
202 goto finish_field;
203 }
204
205 /* get copies of the buffers */
206 np = mh_xstrdup(name);
207 vp = mh_xstrdup(buf);
208
209 /* if necessary, get rest of field */
210 while (state == FLDPLUS) {
211 bufsz = sizeof buf;
212 state = m_getfld2(&gstate, name, buf, &bufsz);
213 vp = add (buf, vp); /* add to previous value */
214 }
215
216 /*
217 * Now add the header data to the list, unless it's an attach
218 * header; in that case, add it to our attach list
219 */
220
221 if (strcasecmp(ATTACH_FIELD, np) == 0 ||
222 strcasecmp(ATTACH_FIELD_ALT, np) == 0) {
223 struct attach_list *entry;
224 char *s = vp, *e = vp + strlen(vp) - 1;
225 free(np);
226
227 /*
228 * Make sure we can find the start of this filename.
229 * If it's blank, we skip completely. Otherwise, strip
230 * off any leading spaces and trailing newlines.
231 */
232
233 while (isspace((unsigned char) *s))
234 s++;
235
236 while (e > s && *e == '\n')
237 *e-- = '\0';
238
239 if (*s == '\0') {
240 free(vp);
241 goto finish_field;
242 }
243
244 NEW(entry);
245 entry->filename = mh_xstrdup(s);
246 entry->next = NULL;
247 free(vp);
248
249 if (attach_tail) {
250 attach_tail->next = entry;
251 attach_tail = entry;
252 } else {
253 attach_head = attach_tail = entry;
254 }
255 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER, np,
256 LEN(MHBUILD_FILE_PSEUDOHEADER)) == 0) {
257 /* E.g.,
258 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
259 */
260 char *type = np + LEN(MHBUILD_FILE_PSEUDOHEADER);
261 char *filename = vp;
262
263 /* vp should begin with a space because m_getfld2()
264 includes the space after the colon in buf. */
265 while (isspace((unsigned char) *filename)) { ++filename; }
266 /* Trim trailing newline and any other whitespace. */
267 rtrim (filename);
268
269 for (convert = convert_head; convert; convert = convert->next) {
270 if (strcasecmp (convert->type, type) == 0) { break; }
271 }
272 if (convert) {
273 if (convert->filename &&
274 strcasecmp (convert->filename, filename)) {
275 die("Multiple %s headers with different files"
276 " not allowed", type);
277 } else {
278 convert->filename = mh_xstrdup(filename);
279 }
280 } else {
281 NEW0(convert);
282 convert->filename = mh_xstrdup(filename);
283 convert->type = mh_xstrdup(type);
284
285 if (convert_tail) {
286 convert_tail->next = convert;
287 } else {
288 convert_head = convert;
289 }
290 convert_tail = convert;
291 }
292
293 free (vp);
294 free (np);
295 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER, np,
296 LEN(MHBUILD_ARGS_PSEUDOHEADER)) == 0) {
297 /* E.g.,
298 * Nmh-mhbuild-args-text/calendar: -reply accept
299 */
300 char *type = np + LEN(MHBUILD_ARGS_PSEUDOHEADER);
301 char *argstring = vp;
302
303 /* vp should begin with a space because m_getfld2()
304 includes the space after the colon in buf. */
305 while (isspace((unsigned char) *argstring)) { ++argstring; }
306 /* Trim trailing newline and any other whitespace. */
307 rtrim (argstring);
308
309 for (convert = convert_head; convert; convert = convert->next) {
310 if (strcasecmp (convert->type, type) == 0) { break; }
311 }
312 if (convert) {
313 if (convert->argstring &&
314 strcasecmp (convert->argstring, argstring)) {
315 die("Multiple %s headers with different "
316 "argstrings not allowed", type);
317 } else {
318 convert->argstring = mh_xstrdup(argstring);
319 }
320 } else {
321 NEW0(convert);
322 convert->type = mh_xstrdup(type);
323 convert->argstring = mh_xstrdup(argstring);
324
325 if (convert_tail) {
326 convert_tail->next = convert;
327 } else {
328 convert_head = convert;
329 }
330 convert_tail = convert;
331 }
332
333 free (vp);
334 free (np);
335 } else {
336 add_header (ct, np, vp);
337 }
338
339 finish_field:
340 /* if this wasn't the last header field, then continue */
341 continue;
342
343 case BODY:
344 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
345 break;
346 case FILEEOF:
347 break;
348
349 case LENERR:
350 case FMTERR:
351 die("message format error in component #%d", compnum);
352
353 default:
354 die("getfld() returned %d", state);
355 }
356 break;
357 }
358 m_getfld_state_destroy (&gstate);
359
360 if (header_encoding != CE_8BIT) {
361 /*
362 * Iterate through the list of headers and call the function to MIME-ify
363 * them if required.
364 */
365
366 for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
367 if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
368 die("Unable to encode header \"%s\"", hp->name);
369 }
370 }
371 }
372
373 /*
374 * Now add the MIME-Version header field
375 * to the list of header fields.
376 */
377
378 if (! dist) {
379 np = mh_xstrdup(VRSN_FIELD);
380 vp = concat (" ", VRSN_VALUE, "\n", NULL);
381 add_header (ct, np, vp);
382 }
383
384 /*
385 * We initially assume we will find multiple contents in the
386 * draft. So create a multipart/mixed content to hold everything.
387 * We can remove this later, if it is not needed.
388 */
389 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
390 done (1);
391 ct->c_type = CT_MULTIPART;
392 ct->c_subtype = MULTI_MIXED;
393
394 NEW0(m);
395 ct->c_ctparams = (void *) m;
396 pp = &m->mp_parts;
397
398 /*
399 * read and parse the composition file
400 * and the directives it contains.
401 */
402 while (fgetstr (buf, sizeof(buf) - 1, in)) {
403 struct part *part;
404 CT p;
405
406 if (user_content (in, buf, &p, infile) == DONE) {
407 inform("ignoring spurious #end, continuing...");
408 continue;
409 }
410 if (!p)
411 continue;
412
413 NEW0(part);
414 *pp = part;
415 pp = &part->mp_next;
416 part->mp_part = p;
417 }
418
419 /*
420 * Add any Attach headers to the list of MIME parts at the end of the
421 * message.
422 */
423
424 for (at_entry = attach_head; at_entry; ) {
425 struct attach_list *at_prev = at_entry;
426 struct part *part;
427 CT p;
428
429 if (access(at_entry->filename, R_OK) != 0) {
430 adios("reading", "Unable to open %s for", at_entry->filename);
431 }
432
433 NEW0(p);
434 init_decoded_content(p, infile);
435
436 /*
437 * Initialize our content structure based on the filename,
438 * and fill in all of the relevant fields. Also place MIME
439 * parameters in the attributes array.
440 */
441
442 setup_attach_content(p, at_entry->filename);
443
444 NEW0(part);
445 *pp = part;
446 pp = &part->mp_next;
447 part->mp_part = p;
448
449 at_entry = at_entry->next;
450 free(at_prev->filename);
451 free(at_prev);
452 }
453
454 /*
455 * Handle the mhbuild pseudoheaders, which deal with specific
456 * content types.
457 */
458 if (convert_head) {
459 CT *ctp;
460 convert_list *next;
461
462 set_done(freects_done);
463
464 /* In case there are multiple calls that land here, prevent leak. */
465 for (ctp = cts; ctp && *ctp; ++ctp) { free_content (*ctp); }
466 free (cts);
467
468 /* Extract the type part (as a CT) from filename. */
469 cts = mh_xcalloc(2, sizeof *cts);
470 if (! (cts[0] = parse_mime (convert_head->filename))) {
471 die("failed to parse %s", convert_head->filename);
472 }
473
474 expand_pseudoheaders (cts[0], m, infile, convert_head);
475
476 /* Free the convert list. */
477 for (convert = convert_head; convert; convert = next) {
478 next = convert->next;
479 free (convert->type);
480 free (convert->filename);
481 free (convert->argstring);
482 free (convert);
483 }
484 convert_head = NULL;
485 }
486
487 /*
488 * To allow for empty message bodies, if we've found NO content at all
489 * yet cook up an empty text/plain part.
490 */
491
492 if (!m->mp_parts) {
493 CT p;
494 struct part *part;
495 struct text *t;
496
497 NEW0(p);
498 init_decoded_content(p, infile);
499
500 if (get_ctinfo ("text/plain", p, 0) == NOTOK)
501 done (1);
502
503 p->c_type = CT_TEXT;
504 p->c_subtype = TEXT_PLAIN;
505 p->c_encoding = CE_7BIT;
506 /*
507 * Sigh. ce_file contains the "decoded" contents of this part.
508 * So this seems like the best option available since we're going
509 * to call scan_content() on this.
510 */
511 p->c_cefile.ce_file = mh_xstrdup("/dev/null");
512 p->c_begin = ftell(in);
513 p->c_end = ftell(in);
514
515 NEW0(t);
516 t->tx_charset = CHARSET_SPECIFIED;
517 p->c_ctparams = t;
518
519 NEW0(part);
520 *pp = part;
521 part->mp_part = p;
522 }
523
524 /*
525 * close the composition draft since
526 * it's not needed any longer.
527 */
528 fclose (in);
529
530 /*
531 * If only one content was found, then remove and
532 * free the outer multipart content.
533 */
534 if (!m->mp_parts->mp_next) {
535 CT p;
536
537 p = m->mp_parts->mp_part;
538 m->mp_parts->mp_part = NULL;
539
540 /* move header fields */
541 p->c_first_hf = ct->c_first_hf;
542 p->c_last_hf = ct->c_last_hf;
543 ct->c_first_hf = NULL;
544 ct->c_last_hf = NULL;
545
546 free_content (ct);
547 ct = p;
548 } else {
549 set_id (ct, 1);
550 }
551
552 /*
553 * Fill out, or expand directives. Parse and execute
554 * commands specified by profile composition strings.
555 */
556 compose_content (ct, verbose);
557
558 if ((cp = strchr(prefix, 'a')) == NULL)
559 die("internal error(4)");
560
561 /*
562 * If using EAI, force 8-bit charset.
563 */
564 if (header_encoding == CE_8BIT) {
565 set_charset (ct, 1);
566 }
567
568 /*
569 * Scan the contents. Choose a transfer encoding, and
570 * check if prefix for multipart boundary clashes with
571 * any of the contents.
572 */
573 while (scan_content (ct, maxunencoded) == NOTOK) {
574 if (*cp < 'z') {
575 (*cp)++;
576 } else {
577 if (*++cp == 0)
578 die("giving up trying to find a unique delimiter string");
579 (*cp)++;
580 }
581 }
582
583 /* Build the rest of the header field structures */
584 if (! dist)
585 build_headers (ct, header_encoding);
586
587 return ct;
588 }
589
590
591 /*
592 * Set up structures for placing unencoded
593 * content when building parts.
594 */
595
596 static int
597 init_decoded_content (CT ct, const char *filename)
598 {
599 ct->c_ceopenfnx = open7Bit; /* since unencoded */
600 ct->c_ceclosefnx = close_encoding;
601 ct->c_cesizefnx = NULL; /* since unencoded */
602 ct->c_encoding = CE_7BIT; /* Seems like a reasonable default */
603 ct->c_file = mh_xstrdup(FENDNULL(filename));
604
605 return OK;
606 }
607
608
609 static char *
610 fgetstr (char *s, int n, FILE *stream)
611 {
612 char *cp, *ep;
613
614 ep = s + n;
615 while(1) {
616 for (cp = s; cp < ep;) {
617 int len;
618
619 if (!fgets (cp, n, stream))
620 return cp == s ? NULL : s; /* "\\\nEOF" ignored. */
621
622 if (! do_direct() || (cp == s && *cp != '#'))
623 return s; /* Plaintext line. */
624
625 len = strlen(cp);
626 if (len <= 1)
627 break; /* Can't contain "\\\n". */
628 cp += len - 1; /* Just before NUL. */
629 if (*cp-- != '\n' || *cp != '\\')
630 break;
631 *cp = '\0'; /* Erase the trailing "\\\n". */
632 n -= (len - 2);
633 }
634
635 if (strcmp(s, "#on\n") == 0) {
636 directive_onoff(1);
637 } else if (strcmp(s, "#off\n") == 0) {
638 directive_onoff(0);
639 } else if (strcmp(s, "#pop\n") == 0) {
640 directive_pop();
641 } else {
642 return s;
643 }
644 }
645 }
646
647
648 /*
649 * Parse the composition draft for text and directives.
650 * Do initial setup of Content structure.
651 */
652
653 static int
654 user_content (FILE *in, char *buf, CT *ctp, const char *infilename)
655 {
656 int extrnal, vrsn;
657 char *cp, **ap;
658 char buffer[NMH_BUFSIZ];
659 struct multipart *m;
660 struct part **pp;
661 struct stat st;
662 struct str2init *s2i;
663 CI ci;
664 CT ct;
665 CE ce;
666
667 if (buf[0] == '\n' || (do_direct() && strcmp (buf, "#\n") == 0)) {
668 *ctp = NULL;
669 return OK;
670 }
671
672 /* allocate basic Content structure */
673 NEW0(ct);
674 *ctp = ct;
675
676 /* allocate basic structure for handling decoded content */
677 init_decoded_content (ct, infilename);
678 ce = &ct->c_cefile;
679
680 ci = &ct->c_ctinfo;
681 set_id (ct, 0);
682
683 /*
684 * Handle inline text. Check if line
685 * is one of the following forms:
686 *
687 * 1) doesn't begin with '#' (implicit directive)
688 * 2) begins with "##" (implicit directive)
689 * 3) begins with "#<"
690 */
691 if (!do_direct() || buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
692 int headers;
693 bool inlineD;
694 long pos;
695 char content[BUFSIZ];
696 FILE *out;
697 char *cp;
698
699 if ((cp = m_mktemp2(NULL, invo_name, NULL, &out)) == NULL) {
700 adios("mhbuildsbr", "unable to create temporary file in %s",
701 get_temp_dir());
702 }
703
704 /* use a temp file to collect the plain text lines */
705 ce->ce_file = mh_xstrdup(cp);
706 ce->ce_unlink = 1;
707
708 if (do_direct() && (buf[0] == '#' && buf[1] == '<')) {
709 strncpy (content, buf + 2, sizeof(content));
710 inlineD = true;
711 goto rock_and_roll;
712 }
713 inlineD = false;
714
715 /* the directive is implicit */
716 strncpy (content, "text/plain", sizeof(content));
717 headers = 0;
718 strncpy (buffer, (!do_direct() || buf[0] != '#') ? buf : buf + 1, sizeof(buffer));
719 for (;;) {
720 int i;
721
722 if (headers >= 0 && do_direct() && uprf (buffer, DESCR_FIELD)
723 && buffer[i = LEN(DESCR_FIELD)] == ':') {
724 headers = 1;
725
726 again_descr:
727 ct->c_descr = add (buffer + i + 1, ct->c_descr);
728 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
729 die("end-of-file after %s: field in plaintext", DESCR_FIELD);
730 switch (buffer[0]) {
731 case ' ':
732 case '\t':
733 i = -1;
734 goto again_descr;
735
736 case '#':
737 die("#-directive after %s: field in plaintext", DESCR_FIELD);
738 /* NOTREACHED */
739
740 default:
741 break;
742 }
743 }
744
745 if (headers >= 0 && do_direct() && uprf (buffer, DISPO_FIELD)
746 && buffer[i = LEN(DISPO_FIELD)] == ':') {
747 headers = 1;
748
749 again_dispo:
750 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
751 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
752 die("end-of-file after %s: field in plaintext", DISPO_FIELD);
753 switch (buffer[0]) {
754 case ' ':
755 case '\t':
756 i = -1;
757 goto again_dispo;
758
759 case '#':
760 die("#-directive after %s: field in plaintext", DISPO_FIELD);
761 /* NOTREACHED */
762
763 default:
764 break;
765 }
766 }
767
768 if (headers != 1 || buffer[0] != '\n')
769 fputs (buffer, out);
770
771 rock_and_roll:
772 headers = -1;
773 pos = ftell (in);
774 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
775 break;
776 if (do_direct() && buffer[0] == '#') {
777 char *bp;
778
779 if (buffer[1] != '#')
780 break;
781 for (cp = (bp = buffer) + 1; *cp; cp++)
782 *bp++ = *cp;
783 *bp = '\0';
784 }
785 }
786
787 if (listsw)
788 ct->c_end = ftell (out);
789 fclose (out);
790
791 /* parse content type */
792 if (get_ctinfo (content, ct, inlineD) == NOTOK)
793 done (1);
794
795 for (s2i = str2cts; s2i->si_key; s2i++)
796 if (!strcasecmp (ci->ci_type, s2i->si_key))
797 break;
798 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
799 s2i++;
800
801 /*
802 * check type specified (possibly implicitly)
803 */
804 switch (ct->c_type = s2i->si_val) {
805 case CT_MESSAGE:
806 if (!strcasecmp (ci->ci_subtype, "rfc822")) {
807 ct->c_encoding = CE_7BIT;
808 goto call_init;
809 }
810 /* FALLTHRU */
811 case CT_MULTIPART:
812 die("it doesn't make sense to define an in-line %s content",
813 ct->c_type == CT_MESSAGE ? "message" : "multipart");
814 /* NOTREACHED */
815
816 default:
817 call_init:
818 if ((ct->c_ctinitfnx = s2i->si_init))
819 (*ct->c_ctinitfnx) (ct);
820 break;
821 }
822
823 if (cp)
824 fseek (in, pos, SEEK_SET);
825 return OK;
826 }
827
828 /*
829 * If we've reached this point, the next line
830 * must be some type of explicit directive.
831 */
832
833 /* check if directive is external-type */
834 extrnal = (buf[1] == '@');
835
836 /* parse directive */
837 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
838 done (1);
839
840 /* check directive against the list of MIME types */
841 for (s2i = str2cts; s2i->si_key; s2i++)
842 if (!strcasecmp (ci->ci_type, s2i->si_key))
843 break;
844
845 /*
846 * Check if the directive specified a valid type.
847 * This will happen if it was one of the following forms:
848 *
849 * #type/subtype (or)
850 * #@type/subtype
851 */
852 if (s2i->si_key) {
853 if (!ci->ci_subtype)
854 die("missing subtype in \"#%s\"", ci->ci_type);
855
856 switch (ct->c_type = s2i->si_val) {
857 case CT_MULTIPART:
858 die("use \"#begin ... #end\" instead of \"#%s/%s\"",
859 ci->ci_type, ci->ci_subtype);
860 /* NOTREACHED */
861
862 case CT_MESSAGE:
863 if (!strcasecmp (ci->ci_subtype, "partial"))
864 die("sorry, \"#%s/%s\" isn't supported",
865 ci->ci_type, ci->ci_subtype);
866 if (!strcasecmp (ci->ci_subtype, "external-body"))
867 die("use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
868 ci->ci_type, ci->ci_subtype);
869 use_forw:
870 die( "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
871 ci->ci_type, ci->ci_subtype);
872 /* NOTREACHED */
873
874 default:
875 if ((ct->c_ctinitfnx = s2i->si_init))
876 (*ct->c_ctinitfnx) (ct);
877 break;
878 }
879
880 /*
881 * #@type/subtype (external types directive)
882 */
883 if (extrnal) {
884 struct exbody *e;
885 CT p;
886
887 if (!ci->ci_magic)
888 die("need external information for \"#@%s/%s\"",
889 ci->ci_type, ci->ci_subtype);
890 p = ct;
891
892 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
893 free (ci->ci_magic);
894 ci->ci_magic = NULL;
895
896 /*
897 * Since we are using the current Content structure to
898 * hold information about the type of the external
899 * reference, we need to create another Content structure
900 * for the message/external-body to wrap it in.
901 */
902 NEW0(ct);
903 init_decoded_content(ct, infilename);
904 *ctp = ct;
905 if (get_ctinfo (buffer, ct, 0) == NOTOK)
906 done (1);
907 ct->c_type = CT_MESSAGE;
908 ct->c_subtype = MESSAGE_EXTERNAL;
909
910 NEW0(e);
911 ct->c_ctparams = (void *) e;
912
913 e->eb_parent = ct;
914 e->eb_content = p;
915 p->c_ctexbody = e;
916
917 if (params_external (ct, 1) == NOTOK)
918 done (1);
919
920 return OK;
921 }
922
923 /* Handle [file] argument */
924 if (ci->ci_magic) {
925 /* check if specifies command to execute */
926 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
927 for (cp = ci->ci_magic + 1; isspace ((unsigned char) *cp); cp++)
928 continue;
929 if (!*cp)
930 die("empty pipe command for #%s directive", ci->ci_type);
931 cp = mh_xstrdup(cp);
932 free (ci->ci_magic);
933 ci->ci_magic = cp;
934 } else {
935 /* record filename of decoded contents */
936 ce->ce_file = ci->ci_magic;
937 if (access (ce->ce_file, R_OK) == NOTOK)
938 adios ("reading", "unable to access %s for", ce->ce_file);
939 if (listsw && stat (ce->ce_file, &st) != NOTOK)
940 ct->c_end = (long) st.st_size;
941 ci->ci_magic = NULL;
942 }
943 return OK;
944 }
945
946 /*
947 * No [file] argument, so check profile for
948 * method to compose content.
949 */
950 cp = context_find_by_type ("compose", ci->ci_type, ci->ci_subtype);
951 if (cp == NULL) {
952 content_error (NULL, ct, "don't know how to compose content");
953 done (1);
954 }
955 ci->ci_magic = mh_xstrdup(cp);
956 return OK;
957 }
958
959 if (extrnal)
960 die("external definition not allowed for \"#%s\"", ci->ci_type);
961
962 /*
963 * Message directive
964 * #forw [+folder] [msgs]
965 */
966 if (!strcasecmp (ci->ci_type, "forw")) {
967 int msgnum;
968 char *folder, *arguments[MAXARGS];
969 struct msgs *mp;
970
971 if (ci->ci_magic) {
972 ap = brkstring (ci->ci_magic, " ", "\n");
973 copyip (ap, arguments, MAXARGS);
974 } else {
975 arguments[0] = "cur";
976 arguments[1] = NULL;
977 }
978 folder = NULL;
979
980 /* search the arguments for a folder name */
981 for (ap = arguments; *ap; ap++) {
982 cp = *ap;
983 if (*cp == '+' || *cp == '@') {
984 if (folder)
985 die("only one folder per #forw directive");
986 folder = pluspath (cp);
987 }
988 }
989
990 /* else, use the current folder */
991 if (!folder)
992 folder = mh_xstrdup(getfolder(1));
993
994 if (!(mp = folder_read (folder, 0)))
995 die("unable to read folder %s", folder);
996 for (ap = arguments; *ap; ap++) {
997 cp = *ap;
998 if (*cp != '+' && *cp != '@')
999 if (!m_convert (mp, cp))
1000 done (1);
1001 }
1002 free (folder);
1003 free_ctinfo (ct);
1004
1005 /*
1006 * If there is more than one message to include, make this
1007 * a content of type "multipart/digest" and insert each message
1008 * as a subpart. If there is only one message, then make this
1009 * a content of type "message/rfc822".
1010 */
1011 if (mp->numsel > 1) {
1012 /* we are forwarding multiple messages */
1013 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
1014 done (1);
1015 ct->c_type = CT_MULTIPART;
1016 ct->c_subtype = MULTI_DIGEST;
1017
1018 NEW0(m);
1019 ct->c_ctparams = (void *) m;
1020 pp = &m->mp_parts;
1021
1022 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1023 if (is_selected(mp, msgnum)) {
1024 struct part *part;
1025 CT p;
1026 CE pe;
1027
1028 NEW0(p);
1029 init_decoded_content (p, infilename);
1030 pe = &p->c_cefile;
1031 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
1032 done (1);
1033 p->c_type = CT_MESSAGE;
1034 p->c_subtype = MESSAGE_RFC822;
1035
1036 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
1037 pe->ce_file = mh_xstrdup(buffer);
1038 if (listsw && stat (pe->ce_file, &st) != NOTOK)
1039 p->c_end = (long) st.st_size;
1040
1041 NEW0(part);
1042 *pp = part;
1043 pp = &part->mp_next;
1044 part->mp_part = p;
1045 }
1046 }
1047 } else {
1048 /* we are forwarding one message */
1049 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
1050 done (1);
1051 ct->c_type = CT_MESSAGE;
1052 ct->c_subtype = MESSAGE_RFC822;
1053
1054 msgnum = mp->lowsel;
1055 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
1056 ce->ce_file = mh_xstrdup(buffer);
1057 if (listsw && stat (ce->ce_file, &st) != NOTOK)
1058 ct->c_end = (long) st.st_size;
1059 }
1060
1061 folder_free (mp); /* free folder/message structure */
1062 return OK;
1063 }
1064
1065 /*
1066 * #end
1067 */
1068 if (!strcasecmp (ci->ci_type, "end")) {
1069 free_content (ct);
1070 *ctp = NULL;
1071 return DONE;
1072 }
1073
1074 /*
1075 * #begin [ alternative | parallel ]
1076 */
1077 if (!strcasecmp (ci->ci_type, "begin")) {
1078 if (!ci->ci_magic) {
1079 vrsn = MULTI_MIXED;
1080 cp = SubMultiPart[vrsn - 1].kv_key;
1081 } else if (!strcasecmp (ci->ci_magic, "alternative")) {
1082 vrsn = MULTI_ALTERNATE;
1083 cp = SubMultiPart[vrsn - 1].kv_key;
1084 } else if (!strcasecmp (ci->ci_magic, "parallel")) {
1085 vrsn = MULTI_PARALLEL;
1086 cp = SubMultiPart[vrsn - 1].kv_key;
1087 } else if (uprf (ci->ci_magic, "digest")) {
1088 goto use_forw;
1089 } else {
1090 vrsn = MULTI_UNKNOWN;
1091 cp = ci->ci_magic;
1092 }
1093
1094 free_ctinfo (ct);
1095 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
1096 if (get_ctinfo (buffer, ct, 0) == NOTOK)
1097 done (1);
1098 ct->c_type = CT_MULTIPART;
1099 ct->c_subtype = vrsn;
1100
1101 NEW0(m);
1102 ct->c_ctparams = (void *) m;
1103
1104 pp = &m->mp_parts;
1105 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
1106 struct part *part;
1107 CT p;
1108
1109 if (user_content (in, buffer, &p, infilename) == DONE) {
1110 if (!m->mp_parts)
1111 die("empty \"#begin ... #end\" sequence");
1112 return OK;
1113 }
1114 if (!p)
1115 continue;
1116
1117 NEW0(part);
1118 *pp = part;
1119 pp = &part->mp_next;
1120 part->mp_part = p;
1121 }
1122 inform("premature end-of-file, missing #end, continuing...");
1123 return OK;
1124 }
1125
1126 /*
1127 * Unknown directive
1128 */
1129 die("unknown directive \"#%s\"", ci->ci_type);
1130 return NOTOK; /* NOT REACHED */
1131 }
1132
1133
1134 static void
1135 set_id (CT ct, int top)
1136 {
1137 char contentid[BUFSIZ];
1138 static int partno;
1139 static time_t clock = 0;
1140 static char *msgfmt;
1141
1142 if (clock == 0) {
1143 time (&clock);
1144 snprintf (contentid, sizeof(contentid), "%s\n", message_id (clock, 1));
1145 partno = 0;
1146 msgfmt = mh_xstrdup(contentid);
1147 }
1148 snprintf (contentid, sizeof(contentid), msgfmt, top ? 0 : ++partno);
1149 ct->c_id = mh_xstrdup(contentid);
1150 }
1151
1152
1153 /*
1154 * Fill out, or expand the various contents in the composition
1155 * draft. Read-in any necessary files. Parse and execute any
1156 * commands specified by profile composition strings.
1157 */
1158
1159 static int
1160 compose_content (CT ct, int verbose)
1161 {
1162 CE ce = &ct->c_cefile;
1163
1164 switch (ct->c_type) {
1165 case CT_MULTIPART:
1166 {
1167 int partnum;
1168 char *pp;
1169 char partnam[BUFSIZ];
1170 struct multipart *m = (struct multipart *) ct->c_ctparams;
1171 struct part *part;
1172
1173 if (ct->c_partno) {
1174 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1175 pp = partnam + strlen (partnam);
1176 } else {
1177 pp = partnam;
1178 }
1179
1180 /* first, we call compose_content on all the subparts */
1181 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
1182 CT p = part->mp_part;
1183
1184 sprintf (pp, "%d", partnum);
1185 p->c_partno = mh_xstrdup(partnam);
1186 if (compose_content (p, verbose) == NOTOK)
1187 return NOTOK;
1188 }
1189
1190 /*
1191 * If the -rfc934mode switch is given, then check all
1192 * the subparts of a multipart/digest. If they are all
1193 * message/rfc822, then mark this content and all
1194 * subparts with the rfc934 compatibility mode flag.
1195 */
1196 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1197 bool is934 = true;
1198
1199 for (part = m->mp_parts; part; part = part->mp_next) {
1200 CT p = part->mp_part;
1201
1202 if (p->c_subtype != MESSAGE_RFC822) {
1203 is934 = false;
1204 break;
1205 }
1206 }
1207 ct->c_rfc934 = is934;
1208 for (part = m->mp_parts; part; part = part->mp_next) {
1209 CT p = part->mp_part;
1210
1211 if ((p->c_rfc934 = is934))
1212 p->c_end++;
1213 }
1214 }
1215
1216 if (listsw) {
1217 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1218 if (ct->c_rfc934)
1219 ct->c_end++;
1220
1221 for (part = m->mp_parts; part; part = part->mp_next)
1222 ct->c_end += part->mp_part->c_end + partnum;
1223 }
1224 }
1225 break;
1226
1227 case CT_MESSAGE:
1228 /* Nothing to do for type message */
1229 break;
1230
1231 /*
1232 * Discrete types (text/application/audio/image/video)
1233 */
1234 default:
1235 if (!ce->ce_file) {
1236 pid_t child_id;
1237 bool xstdout;
1238 int len, buflen;
1239 char *bp, *cp;
1240 char *vec[4], buffer[BUFSIZ];
1241 FILE *out;
1242 CI ci = &ct->c_ctinfo;
1243 char *tfile = NULL;
1244
1245 if (!(cp = ci->ci_magic))
1246 die("internal error(5)");
1247
1248 if ((tfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1249 adios("mhbuildsbr", "unable to create temporary file in %s",
1250 get_temp_dir());
1251 }
1252 ce->ce_file = mh_xstrdup(tfile);
1253 ce->ce_unlink = 1;
1254
1255 xstdout = false;
1256
1257 /* Get buffer ready to go */
1258 bp = buffer;
1259 bp[0] = '\0';
1260 buflen = sizeof(buffer);
1261
1262 /*
1263 * Parse composition string into buffer
1264 */
1265 for ( ; *cp; cp++) {
1266 if (*cp == '%') {
1267 switch (*++cp) {
1268 case 'a':
1269 {
1270 /* insert parameters from directive */
1271 char *s = "";
1272 PM pm;
1273
1274 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1275 snprintf (bp, buflen, "%s%s=\"%s\"", s,
1276 pm->pm_name, get_param_value(pm, '?'));
1277 len = strlen (bp);
1278 bp += len;
1279 buflen -= len;
1280 s = " ";
1281 }
1282 }
1283 break;
1284
1285 case 'F':
1286 /* %f, and stdout is not-redirected */
1287 xstdout = true;
1288 /* FALLTHRU */
1289
1290 case 'f':
1291 /*
1292 * insert temporary filename where
1293 * content should be written
1294 */
1295 snprintf (bp, buflen, "%s", ce->ce_file);
1296 break;
1297
1298 case 's':
1299 /* insert content subtype */
1300 strncpy (bp, ci->ci_subtype, buflen);
1301 break;
1302
1303 case '%':
1304 /* insert character % */
1305 goto raw;
1306
1307 default:
1308 *bp++ = *--cp;
1309 *bp = '\0';
1310 buflen--;
1311 continue;
1312 }
1313 len = strlen (bp);
1314 bp += len;
1315 buflen -= len;
1316 } else {
1317 raw:
1318 *bp++ = *cp;
1319 *bp = '\0';
1320 buflen--;
1321 }
1322 }
1323
1324 if (verbose)
1325 printf ("composing content %s/%s from command\n\t%s\n",
1326 ci->ci_type, ci->ci_subtype, buffer);
1327
1328 fflush (stdout); /* not sure if need for -noverbose */
1329
1330 vec[0] = "/bin/sh";
1331 vec[1] = "-c";
1332 vec[2] = buffer;
1333 vec[3] = NULL;
1334
1335 if ((out = fopen (ce->ce_file, "w")) == NULL)
1336 adios (ce->ce_file, "unable to open for writing");
1337
1338 child_id = fork();
1339 switch (child_id) {
1340 case NOTOK:
1341 adios ("fork", "unable to fork");
1342 /* NOTREACHED */
1343
1344 case OK:
1345 if (!xstdout)
1346 dup2 (fileno (out), 1);
1347 close (fileno (out));
1348 execvp ("/bin/sh", vec);
1349 fprintf (stderr, "unable to exec ");
1350 perror ("/bin/sh");
1351 _exit(1);
1352 /* NOTREACHED */
1353
1354 default:
1355 fclose (out);
1356 if (pidXwait(child_id, NULL))
1357 done (1);
1358 break;
1359 }
1360 }
1361
1362 /* Check size of file */
1363 if (listsw && ct->c_end == 0L) {
1364 struct stat st;
1365
1366 if (stat (ce->ce_file, &st) != NOTOK)
1367 ct->c_end = (long) st.st_size;
1368 }
1369 break;
1370 }
1371
1372 return OK;
1373 }
1374
1375
1376 /*
1377 * Scan the content.
1378 *
1379 * 1) choose a transfer encoding.
1380 * 2) check for clashes with multipart boundary string.
1381 * 3) for text content, figure out which character set is being used.
1382 *
1383 * If there is a clash with one of the contents and the multipart boundary,
1384 * this function will exit with NOTOK. This will cause the scanning process
1385 * to be repeated with a different multipart boundary. It is possible
1386 * (although highly unlikely) that this scan will be repeated multiple times.
1387 */
1388
1389 static int
1390 scan_content (CT ct, size_t maxunencoded)
1391 {
1392 int prefix_len;
1393 bool check8bit = false, contains8bit = false; /* check if contains 8bit data */
1394 bool checknul = false, containsnul = false; /* check if contains NULs */
1395 bool checklinelen = false, linelen = false; /* check for long lines */
1396 bool checkllinelen = false; /* check for extra-long lines */
1397 bool checkboundary = false, boundaryclash = false; /* check if clashes with multipart boundary */
1398 bool checklinespace = false, linespace = false; /* check if any line ends with space */
1399 char *cp = NULL;
1400 char *bufp = NULL;
1401 size_t buflen;
1402 ssize_t gotlen;
1403 struct text *t = NULL;
1404 FILE *in = NULL;
1405 CE ce = &ct->c_cefile;
1406
1407 /*
1408 * handle multipart by scanning all subparts
1409 * and then checking their encoding.
1410 */
1411 if (ct->c_type == CT_MULTIPART) {
1412 struct multipart *m = (struct multipart *) ct->c_ctparams;
1413 struct part *part;
1414
1415 /* initially mark the domain of enclosing multipart as 7bit */
1416 ct->c_encoding = CE_7BIT;
1417
1418 for (part = m->mp_parts; part; part = part->mp_next) {
1419 CT p = part->mp_part;
1420
1421 if (scan_content (p, maxunencoded) == NOTOK) /* choose encoding for subpart */
1422 return NOTOK;
1423
1424 /* if necessary, enlarge encoding for enclosing multipart */
1425 if (p->c_encoding == CE_BINARY)
1426 ct->c_encoding = CE_BINARY;
1427 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1428 ct->c_encoding = CE_8BIT;
1429 }
1430
1431 return OK;
1432 }
1433
1434 /*
1435 * Decide what to check while scanning this content. Note that
1436 * for text content we always check for 8bit characters if the
1437 * charset is unspecified, because that controls whether or not the
1438 * character set is us-ascii or retrieved from the locale. And
1439 * we check even if the charset is specified, to allow setting
1440 * the proper Content-Transfer-Encoding.
1441 */
1442
1443 if (ct->c_type == CT_TEXT) {
1444 t = (struct text *) ct->c_ctparams;
1445 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1446 checknul = true;
1447 }
1448 check8bit = true;
1449 }
1450
1451 switch (ct->c_reqencoding) {
1452 case CE_8BIT:
1453 checkllinelen = true;
1454 checkboundary = true;
1455 break;
1456 case CE_QUOTED:
1457 checkboundary = true;
1458 break;
1459 case CE_BASE64:
1460 break;
1461 case CE_UNKNOWN:
1462 /* Use the default rules based on content-type */
1463 switch (ct->c_type) {
1464 case CT_TEXT:
1465 checkboundary = true;
1466 checklinelen = true;
1467 if (ct->c_subtype == TEXT_PLAIN) {
1468 checklinespace = false;
1469 } else {
1470 checklinespace = true;
1471 }
1472 break;
1473
1474 case CT_APPLICATION:
1475 check8bit = true;
1476 checknul = true;
1477 checklinelen = true;
1478 checklinespace = true;
1479 checkboundary = true;
1480 break;
1481
1482 case CT_MESSAGE:
1483 checklinelen = false;
1484 checklinespace = false;
1485
1486 /* don't check anything for message/external */
1487 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1488 checkboundary = false;
1489 check8bit = false;
1490 } else {
1491 checkboundary = true;
1492 check8bit = true;
1493 }
1494 break;
1495
1496 case CT_AUDIO:
1497 case CT_IMAGE:
1498 case CT_VIDEO:
1499 /*
1500 * Don't check anything for these types,
1501 * since we are forcing use of base64, unless
1502 * the content-type was specified by a mhbuild directive.
1503 */
1504 check8bit = false;
1505 checklinelen = false;
1506 checklinespace = false;
1507 checkboundary = false;
1508 break;
1509 }
1510 }
1511
1512 /*
1513 * Scan the unencoded content
1514 */
1515 if (check8bit || checklinelen || checklinespace || checkboundary ||
1516 checkllinelen || checknul) {
1517 if ((in = fopen (ce->ce_file, "r")) == NULL)
1518 adios (ce->ce_file, "unable to open for reading");
1519 prefix_len = strlen (prefix);
1520
1521 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1522 /*
1523 * Check for 8bit and NUL data.
1524 */
1525 for (cp = bufp; (check8bit || checknul) &&
1526 cp < bufp + gotlen; cp++) {
1527 if (!isascii ((unsigned char) *cp)) {
1528 contains8bit = true;
1529 check8bit = false; /* no need to keep checking */
1530 }
1531 if (!*cp) {
1532 containsnul = true;
1533 checknul = false; /* no need to keep checking */
1534 }
1535 }
1536
1537 /*
1538 * Check line length.
1539 */
1540 if (checklinelen && ((size_t)gotlen > maxunencoded + 1)) {
1541 linelen = true;
1542 checklinelen = false; /* no need to keep checking */
1543 }
1544
1545 /*
1546 * RFC 5322 specifies that a message cannot contain a line
1547 * greater than 998 characters (excluding the CRLF). If we
1548 * get one of those lines and linelen is NOT set, then abort.
1549 */
1550
1551 if (checkllinelen && !linelen &&
1552 (gotlen > MAXLONGLINE + 1)) {
1553 die("Line in content exceeds maximum line limit (%d)",
1554 MAXLONGLINE);
1555 }
1556
1557 /*
1558 * Check if line ends with a space.
1559 */
1560 if (checklinespace && (cp = bufp + gotlen - 2) > bufp &&
1561 isspace ((unsigned char) *cp)) {
1562 linespace = true;
1563 checklinespace = false; /* no need to keep checking */
1564 }
1565
1566 /*
1567 * Check if content contains a line that clashes
1568 * with our standard boundary for multipart messages.
1569 */
1570 if (checkboundary && bufp[0] == '-' && bufp[1] == '-') {
1571 for (cp = bufp + gotlen - 1; cp >= bufp; cp--)
1572 if (!isspace ((unsigned char) *cp))
1573 break;
1574 *++cp = '\0';
1575 if (!strncmp(bufp + 2, prefix, prefix_len) &&
1576 isdigit((unsigned char) bufp[2 + prefix_len])) {
1577 boundaryclash = true;
1578 checkboundary = false; /* no need to keep checking */
1579 }
1580 }
1581 }
1582 fclose (in);
1583 free(bufp);
1584 }
1585
1586 /*
1587 * If the content is text and didn't specify a character set,
1588 * we need to figure out which one was used.
1589 */
1590 set_charset (ct, contains8bit);
1591
1592 /*
1593 * Decide which transfer encoding to use.
1594 */
1595
1596 if (ct->c_reqencoding != CE_UNKNOWN)
1597 ct->c_encoding = ct->c_reqencoding;
1598 else {
1599 int wants_q_p = (containsnul || linelen || linespace || checksw);
1600
1601 switch (ct->c_type) {
1602 case CT_TEXT:
1603 if (wants_q_p)
1604 ct->c_encoding = CE_QUOTED;
1605 else if (contains8bit)
1606 ct->c_encoding = CE_8BIT;
1607 else
1608 ct->c_encoding = CE_7BIT;
1609
1610 break;
1611
1612 case CT_APPLICATION:
1613 /* For application type, use base64, except when postscript */
1614 if (wants_q_p || contains8bit) {
1615 if (ct->c_subtype == APPLICATION_POSTSCRIPT)
1616 ct->c_encoding = CE_QUOTED; /* historical */
1617 else
1618 ct->c_encoding = CE_BASE64;
1619 } else {
1620 ct->c_encoding = CE_7BIT;
1621 }
1622 break;
1623
1624 case CT_MESSAGE:
1625 ct->c_encoding = contains8bit ? CE_8BIT : CE_7BIT;
1626 break;
1627
1628 case CT_AUDIO:
1629 case CT_IMAGE:
1630 case CT_VIDEO:
1631 /* For audio, image, and video contents, just use base64 */
1632 ct->c_encoding = CE_BASE64;
1633 break;
1634 }
1635 }
1636
1637 return boundaryclash ? NOTOK : OK;
1638 }
1639
1640
1641 /*
1642 * Scan the content structures, and build header
1643 * fields that will need to be output into the
1644 * message.
1645 */
1646
1647 static int
1648 build_headers (CT ct, int header_encoding)
1649 {
1650 int cc, mailbody, extbody, len;
1651 char *np, *vp, buffer[BUFSIZ];
1652 CI ci = &ct->c_ctinfo;
1653
1654 /*
1655 * If message is type multipart, then add the multipart
1656 * boundary to the list of attribute/value pairs.
1657 */
1658 if (ct->c_type == CT_MULTIPART) {
1659 static int level = 0; /* store nesting level */
1660
1661 snprintf (buffer, sizeof(buffer), "%s%d", prefix, level++);
1662 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "boundary", buffer, 0);
1663 }
1664
1665 /*
1666 * Skip the output of Content-Type, parameters, content
1667 * description and disposition, and Content-ID if the
1668 * content is of type "message" and the rfc934 compatibility
1669 * flag is set (which means we are inside multipart/digest
1670 * and the switch -rfc934mode was given).
1671 */
1672 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1673 goto skip_headers;
1674
1675 /*
1676 * output the content type and subtype
1677 */
1678 np = mh_xstrdup(TYPE_FIELD);
1679 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1680
1681 /* keep track of length of line */
1682 len = LEN(TYPE_FIELD) + strlen (ci->ci_type)
1683 + strlen (ci->ci_subtype) + 3;
1684
1685 extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
1686 mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
1687
1688 /*
1689 * Append the attribute/value pairs to
1690 * the end of the Content-Type line.
1691 */
1692
1693 if (ci->ci_first_pm) {
1694 char *s = output_params(len, ci->ci_first_pm, &len, mailbody);
1695
1696 if (!s)
1697 die("Internal error: failed outputting Content-Type "
1698 "parameters");
1699
1700 vp = add (s, vp);
1701 free(s);
1702 }
1703
1704 /*
1705 * Append any RFC-822 comment to the end of
1706 * the Content-Type line.
1707 */
1708 if (ci->ci_comment) {
1709 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1710 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1711 vp = add ("\n\t", vp);
1712 len = 8;
1713 } else {
1714 vp = add (" ", vp);
1715 len++;
1716 }
1717 vp = add (buffer, vp);
1718 len += cc;
1719 }
1720 vp = add ("\n", vp);
1721 add_header (ct, np, vp);
1722
1723 /*
1724 * output the Content-ID, unless disabled by -nocontentid. Note that
1725 * RFC 2045 always requires a Content-ID header for message/external-body
1726 * entities.
1727 */
1728 if ((contentidsw || ct->c_ctexbody) && ct->c_id) {
1729 np = mh_xstrdup(ID_FIELD);
1730 vp = concat (" ", ct->c_id, NULL);
1731 add_header (ct, np, vp);
1732 }
1733 /*
1734 * output the Content-Description
1735 */
1736 if (ct->c_descr) {
1737 np = mh_xstrdup(DESCR_FIELD);
1738 vp = concat (" ", ct->c_descr, NULL);
1739 if (header_encoding != CE_8BIT) {
1740 if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL)) {
1741 die("Unable to encode %s header", DESCR_FIELD);
1742 }
1743 }
1744 add_header (ct, np, vp);
1745 }
1746
1747 /*
1748 * output the Content-Disposition. If it's NULL but c_dispo_type is
1749 * set, then we need to build it.
1750 */
1751 if (ct->c_dispo) {
1752 np = mh_xstrdup(DISPO_FIELD);
1753 vp = concat (" ", ct->c_dispo, NULL);
1754 add_header (ct, np, vp);
1755 } else if (ct->c_dispo_type) {
1756 vp = concat (" ", ct->c_dispo_type, NULL);
1757 len = LEN(DISPO_FIELD) + strlen(vp) + 1;
1758 np = output_params(len, ct->c_dispo_first, NULL, 0);
1759 vp = add(np, vp);
1760 vp = add("\n", vp);
1761 free(np);
1762 add_header (ct, mh_xstrdup(DISPO_FIELD), vp);
1763 }
1764
1765 skip_headers:
1766 /*
1767 * If this is the internal content structure for a
1768 * "message/external", then we are done with the
1769 * headers (since it has no body).
1770 */
1771 if (ct->c_ctexbody)
1772 return OK;
1773
1774 /*
1775 * output the Content-MD5
1776 */
1777 if (checksw) {
1778 np = mh_xstrdup(MD5_FIELD);
1779 vp = calculate_digest (ct, ct->c_encoding == CE_QUOTED);
1780 add_header (ct, np, vp);
1781 }
1782
1783 /*
1784 * output the Content-Transfer-Encoding
1785 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1786 */
1787 if (header_encoding == CE_8BIT && ct->c_encoding == CE_7BIT) {
1788 ct->c_encoding = CE_8BIT;
1789 }
1790
1791 switch (ct->c_encoding) {
1792 case CE_7BIT:
1793 /* Nothing to output */
1794 break;
1795
1796 case CE_8BIT:
1797 np = mh_xstrdup(ENCODING_FIELD);
1798 vp = concat (" ", "8bit", "\n", NULL);
1799 add_header (ct, np, vp);
1800 break;
1801
1802 case CE_QUOTED:
1803 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1804 die("internal error, invalid encoding");
1805
1806 np = mh_xstrdup(ENCODING_FIELD);
1807 vp = concat (" ", "quoted-printable", "\n", NULL);
1808 add_header (ct, np, vp);
1809 break;
1810
1811 case CE_BASE64:
1812 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1813 die("internal error, invalid encoding");
1814
1815 np = mh_xstrdup(ENCODING_FIELD);
1816 vp = concat (" ", "base64", "\n", NULL);
1817 add_header (ct, np, vp);
1818 break;
1819
1820 case CE_BINARY:
1821 if (ct->c_type == CT_MESSAGE)
1822 die("internal error, invalid encoding");
1823
1824 np = mh_xstrdup(ENCODING_FIELD);
1825 vp = concat (" ", "binary", "\n", NULL);
1826 add_header (ct, np, vp);
1827 break;
1828
1829 default:
1830 die("unknown transfer encoding in content");
1831 break;
1832 }
1833
1834 /*
1835 * Additional content specific header processing
1836 */
1837 switch (ct->c_type) {
1838 case CT_MULTIPART:
1839 {
1840 struct multipart *m;
1841 struct part *part;
1842
1843 m = (struct multipart *) ct->c_ctparams;
1844 for (part = m->mp_parts; part; part = part->mp_next) {
1845 CT p;
1846
1847 p = part->mp_part;
1848 build_headers (p, header_encoding);
1849 }
1850 }
1851 break;
1852
1853 case CT_MESSAGE:
1854 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1855 struct exbody *e;
1856
1857 e = (struct exbody *) ct->c_ctparams;
1858 build_headers (e->eb_content, header_encoding);
1859 }
1860 break;
1861
1862 default:
1863 /* Nothing to do */
1864 break;
1865 }
1866
1867 return OK;
1868 }
1869
1870
1871 static char nib2b64[0x40+1] =
1872 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1873
1874 static char *
1875 calculate_digest (CT ct, int asciiP)
1876 {
1877 int cc;
1878 char *vp, *op;
1879 unsigned char *dp;
1880 unsigned char digest[16];
1881 unsigned char outbuf[25];
1882 MD5_CTX mdContext;
1883 CE ce = &ct->c_cefile;
1884 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1885 FILE *in;
1886
1887 /* open content */
1888 if ((in = fopen (infilename, "r")) == NULL)
1889 adios (infilename, "unable to open for reading");
1890
1891 /* Initialize md5 context */
1892 MD5Init (&mdContext);
1893
1894 /* calculate md5 message digest */
1895 if (asciiP) {
1896 char *bufp = NULL;
1897 size_t buflen;
1898 ssize_t gotlen;
1899 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1900 char c, *cp;
1901
1902 cp = bufp + gotlen - 1;
1903 if ((c = *cp) == '\n')
1904 gotlen--;
1905
1906 MD5Update (&mdContext, (unsigned char *) bufp,
1907 (unsigned int) gotlen);
1908
1909 if (c == '\n')
1910 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1911 }
1912 } else {
1913 char buffer[BUFSIZ];
1914 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1915 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1916 }
1917
1918 /* md5 finalization. Write digest and zero md5 context */
1919 MD5Final (digest, &mdContext);
1920
1921 /* close content */
1922 fclose (in);
1923
1924 /* print debugging info */
1925 if (debugsw) {
1926 unsigned char *ep;
1927
1928 fprintf (stderr, "MD5 digest=");
1929 for (ep = (dp = digest) + sizeof digest;
1930 dp < ep; dp++)
1931 fprintf (stderr, "%02x", *dp & 0xff);
1932 fprintf (stderr, "\n");
1933 }
1934
1935 /* encode the digest using base64 */
1936 for (dp = digest, op = (char *) outbuf,
1937 cc = sizeof digest;
1938 cc > 0; cc -= 3, op += 4) {
1939 unsigned long bits;
1940 char *bp;
1941
1942 bits = (*dp++ & 0xff) << 16;
1943 if (cc > 1) {
1944 bits |= (*dp++ & 0xff) << 8;
1945 if (cc > 2)
1946 bits |= *dp++ & 0xff;
1947 }
1948
1949 for (bp = op + 4; bp > op; bits >>= 6)
1950 *--bp = nib2b64[bits & 0x3f];
1951 if (cc < 3) {
1952 *(op + 3) = '=';
1953 if (cc < 2)
1954 *(op + 2) = '=';
1955 }
1956 }
1957
1958 /* null terminate string */
1959 outbuf[24] = '\0';
1960
1961 /* now make copy and return string */
1962 vp = concat (" ", outbuf, "\n", NULL);
1963 return vp;
1964 }
1965
1966 /*
1967 * Set things up for the content structure for file "filename" that
1968 * we want to attach
1969 */
1970
1971 static void
1972 setup_attach_content(CT ct, char *filename)
1973 {
1974 char *type, *simplename = r1bindex(filename, '/');
1975 struct str2init *s2i;
1976 PM pm;
1977
1978 if (! (type = mime_type(filename))) {
1979 die("Unable to determine MIME type of \"%s\"", filename);
1980 }
1981
1982 /*
1983 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1984 * since we're just feeding it a MIME type we have to add those ourselves.
1985 * Map that to a valid content-type label and call any initialization
1986 * function.
1987 */
1988
1989 if (get_ctinfo(type, ct, 0) == NOTOK)
1990 done(1);
1991
1992 free(type);
1993
1994 for (s2i = str2cts; s2i->si_key; s2i++)
1995 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
1996 break;
1997 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
1998 s2i++;
1999
2000 /*
2001 * Make sure the type isn't incompatible with what we can handle
2002 */
2003
2004 switch (ct->c_type = s2i->si_val) {
2005 case CT_MULTIPART:
2006 die("multipart types must be specified by mhbuild directives");
2007 /* NOTREACHED */
2008
2009 case CT_MESSAGE:
2010 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
2011 die("Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
2012 ct->c_ctinfo.ci_subtype);
2013 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
2014 die("external-body messages must be specified "
2015 "by mhbuild directives");
2016 /* FALLTHRU */
2017
2018 default:
2019 /*
2020 * This sets the subtype, if it's significant
2021 */
2022 if ((ct->c_ctinitfnx = s2i->si_init))
2023 (*ct->c_ctinitfnx)(ct);
2024 break;
2025 }
2026
2027 /*
2028 * Feed in a few attributes; specifically, the name attribute, the
2029 * content-description, and the content-disposition.
2030 */
2031
2032 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
2033 if (strcasecmp(pm->pm_name, "name") == 0) {
2034 free(pm->pm_value);
2035 pm->pm_value = mh_xstrdup(simplename);
2036 break;
2037 }
2038 }
2039
2040 if (pm == NULL)
2041 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
2042 "name", simplename, 0);
2043
2044 ct->c_descr = mh_xstrdup(simplename);
2045 ct->c_descr = add("\n", ct->c_descr);
2046 ct->c_cefile.ce_file = mh_xstrdup(filename);
2047
2048 set_disposition (ct);
2049
2050 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
2051 }
2052
2053 /*
2054 * If disposition type hasn't already been set in ct:
2055 * Look for mhbuild-disposition-<type>/<subtype> entry
2056 * that specifies Content-Disposition type. Only
2057 * 'attachment' and 'inline' are allowed. Default to
2058 * 'attachment'.
2059 */
2060 void
2061 set_disposition (CT ct)
2062 {
2063 if (ct->c_dispo_type == NULL) {
2064 char *cp = context_find_by_type ("disposition", ct->c_ctinfo.ci_type,
2065 ct->c_ctinfo.ci_subtype);
2066
2067 if (cp && strcasecmp (cp, "attachment") &&
2068 strcasecmp (cp, "inline")) {
2069 inform("configuration problem: %s-disposition-%s%s%s specifies "
2070 "'%s' but only 'attachment' and 'inline' are allowed, "
2071 "continuing...", invo_name,
2072 ct->c_ctinfo.ci_type,
2073 ct->c_ctinfo.ci_subtype ? "/" : "",
2074 FENDNULL(ct->c_ctinfo.ci_subtype),
2075 cp);
2076 }
2077
2078 if (!cp)
2079 cp = "attachment";
2080 ct->c_dispo_type = mh_xstrdup(cp);
2081 }
2082 }
2083
2084 /*
2085 * Set text content charset if it was unspecified. contains8bit
2086 * selections:
2087 * 0: content does not contain 8-bit characters
2088 * 1: content contains 8-bit characters
2089 * -1: ignore content and use user's locale to determine charset
2090 */
2091 void
2092 set_charset (CT ct, int contains8bit)
2093 {
2094 if (ct->c_type == CT_TEXT) {
2095 struct text *t;
2096
2097 if (ct->c_ctparams == NULL) {
2098 NEW0(t);
2099 ct->c_ctparams = t;
2100 t->tx_charset = CHARSET_UNSPECIFIED;
2101 } else {
2102 t = (struct text *) ct->c_ctparams;
2103 }
2104
2105 if (t->tx_charset == CHARSET_UNSPECIFIED) {
2106 CI ci = &ct->c_ctinfo;
2107 char *eightbitcharset = write_charset_8bit();
2108 char *charset = contains8bit ? eightbitcharset : "us-ascii";
2109
2110 if (contains8bit == 1 &&
2111 strcasecmp (eightbitcharset, "US-ASCII") == 0) {
2112 die("Text content contains 8 bit characters, but "
2113 "character set is US-ASCII");
2114 }
2115
2116 add_param (&ci->ci_first_pm, &ci->ci_last_pm, "charset", charset,
2117 0);
2118
2119 t->tx_charset = CHARSET_SPECIFIED;
2120 }
2121 }
2122 }
2123
2124
2125 /*
2126 * Look at all of the replied-to message parts and expand any that
2127 * are matched by a pseudoheader. Except don't descend into
2128 * message parts.
2129 */
2130 void
2131 expand_pseudoheaders (CT ct, struct multipart *m, const char *infile,
2132 const convert_list *convert_head)
2133 {
2134 /* text_plain_ct is used to concatenate all of the text/plain
2135 replies into one part, instead of having each one in a separate
2136 part. */
2137 CT text_plain_ct = NULL;
2138
2139 switch (ct->c_type) {
2140 case CT_MULTIPART: {
2141 struct multipart *mp = (struct multipart *) ct->c_ctparams;
2142 struct part *part;
2143
2144 if (ct->c_subtype == MULTI_ALTERNATE) {
2145 bool matched = false;
2146
2147 /* The parts are in descending priority order (defined by
2148 RFC 2046 Sec. 5.1.4) because they were reversed by
2149 parse_mime (). So, stop looking for matches with
2150 immediate subparts after the first match of an
2151 alternative. */
2152 for (part = mp->mp_parts; ! matched && part; part = part->mp_next) {
2153 char *type_subtype =
2154 concat (part->mp_part->c_ctinfo.ci_type, "/",
2155 part->mp_part->c_ctinfo.ci_subtype, NULL);
2156
2157 if (part->mp_part->c_type == CT_MULTIPART) {
2158 expand_pseudoheaders (part->mp_part, m, infile,
2159 convert_head);
2160 } else {
2161 const convert_list *c;
2162
2163 for (c = convert_head; c; c = c->next) {
2164 if (! strcasecmp (type_subtype, c->type)) {
2165 expand_pseudoheader (part->mp_part, &text_plain_ct,
2166 m, infile,
2167 c->type, c->argstring);
2168 matched = true;
2169 break;
2170 }
2171 }
2172 }
2173 free (type_subtype);
2174 }
2175 } else {
2176 for (part = mp->mp_parts; part; part = part->mp_next) {
2177 expand_pseudoheaders (part->mp_part, m, infile, convert_head);
2178 }
2179 }
2180 break;
2181 }
2182
2183 default: {
2184 char *type_subtype =
2185 concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
2186 NULL);
2187 const convert_list *c;
2188
2189 for (c = convert_head; c; c = c->next) {
2190 if (! strcasecmp (type_subtype, c->type)) {
2191 expand_pseudoheader (ct, &text_plain_ct, m, infile, c->type,
2192 c->argstring);
2193 break;
2194 }
2195 }
2196 free (type_subtype);
2197 break;
2198 }
2199 }
2200 }
2201
2202
2203 /*
2204 * Expand a single pseudoheader. It's for the specified type.
2205 */
2206 void
2207 expand_pseudoheader (CT ct, CT *text_plain_ct, struct multipart *m,
2208 const char *infile, const char *type,
2209 const char *argstring)
2210 {
2211 char *reply_file;
2212 FILE *reply_fp = NULL;
2213 char *convert, *type_p, *subtype_p;
2214 char *convert_command;
2215 char *charset = NULL;
2216 char *cp;
2217 struct str2init *s2i;
2218 CT reply_ct;
2219 struct part *part;
2220 int status;
2221
2222 type_p = getcpy (type);
2223 if ((subtype_p = strchr (type_p, '/'))) {
2224 *subtype_p++ = '\0';
2225 convert = context_find_by_type ("convert", type_p, subtype_p);
2226 } else {
2227 free (type_p);
2228 type_p = concat ("mhbuild-convert-", type, NULL);
2229 convert = context_find (type_p);
2230 }
2231 free (type_p);
2232
2233 if (! (convert)) {
2234 /* No mhbuild-convert- entry in mhn.defaults or profile for type. */
2235 return;
2236 }
2237 /* reply_file is used to pass the output of the convert. */
2238 reply_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2239 convert_command =
2240 concat (convert, " ", FENDNULL(argstring), " >", reply_file, NULL);
2241
2242 /* Convert here . . . */
2243 ct->c_storeproc = mh_xstrdup(convert_command);
2244 ct->c_umask = ~m_gmprot ();
2245
2246 if ((status = show_content_aux (ct, 0, convert_command, NULL, NULL)) !=
2247 OK) {
2248 inform("store of %s content failed, continuing...", type);
2249 }
2250 free (convert_command);
2251
2252 /* Fill out the the new ct, reply_ct. */
2253 NEW0(reply_ct);
2254 init_decoded_content (reply_ct, infile);
2255
2256 if (extract_headers (reply_ct, reply_file, &reply_fp) == NOTOK) {
2257 inform("failed to extract headers from convert output in %s, "
2258 "continuing...", reply_file);
2259 free(reply_file);
2260 return;
2261 }
2262
2263 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2264 charset can't be handled natively. */
2265 for (s2i = str2cts; s2i->si_key; s2i++) {
2266 if (strcasecmp(reply_ct->c_ctinfo.ci_type, s2i->si_key) == 0) {
2267 break;
2268 }
2269 }
2270
2271 if ((reply_ct->c_ctinitfnx = s2i->si_init)) {
2272 (*reply_ct->c_ctinitfnx)(reply_ct);
2273 }
2274
2275 if ((cp = get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1))) {
2276 /* The reply Content-Type had the charset. */
2277 charset = cp;
2278 } else {
2279 set_charset (reply_ct, -1);
2280 charset = get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1);
2281 }
2282
2283 /* Concatenate text/plain parts. */
2284 if (reply_ct->c_type == CT_TEXT && reply_ct->c_subtype == TEXT_PLAIN) {
2285 if (! *text_plain_ct && m->mp_parts && m->mp_parts->mp_part &&
2286 m->mp_parts->mp_part->c_type == CT_TEXT &&
2287 m->mp_parts->mp_part->c_subtype == TEXT_PLAIN) {
2288 *text_plain_ct = m->mp_parts->mp_part;
2289 /* Make sure that the charset is set in the text/plain part. */
2290 set_charset (*text_plain_ct, -1);
2291 }
2292
2293 if (*text_plain_ct) {
2294 /* Only concatenate if the charsets are identical. */
2295 char *text_plain_ct_charset =
2296 get_param ((*text_plain_ct)->c_ctinfo.ci_first_pm, "charset",
2297 '?', 1);
2298
2299 if (strcasecmp (text_plain_ct_charset, charset) == 0) {
2300 /* Append this text/plain reply to the first one.
2301 If there's a problem anywhere along the way,
2302 instead attach it is a separate part. */
2303 int text_plain_reply =
2304 open ((*text_plain_ct)->c_cefile.ce_file,
2305 O_WRONLY | O_APPEND);
2306 int addl_reply = open (reply_file, O_RDONLY);
2307
2308 if (text_plain_reply != NOTOK && addl_reply != NOTOK) {
2309 /* Insert blank line before each addl part. */
2310 /* It would be nice not to do this for the first one. */
2311 if (write (text_plain_reply, "\n", 1) == 1) {
2312 /* Copy the text from the new reply and
2313 then free its Content struct. */
2314 cpydata (addl_reply, text_plain_reply,
2315 (*text_plain_ct)->c_cefile.ce_file,
2316 reply_file);
2317 if (close (text_plain_reply) == OK &&
2318 close (addl_reply) == OK) {
2319 /* If appended text needed 8-bit but first text didn't,
2320 propagate the 8-bit indication. */
2321 if ((*text_plain_ct)->c_reqencoding == CE_7BIT &&
2322 reply_ct->c_reqencoding == CE_8BIT) {
2323 (*text_plain_ct)->c_reqencoding = CE_8BIT;
2324 }
2325
2326 if (reply_fp) { fclose (reply_fp); }
2327 free (reply_file);
2328 free_content (reply_ct);
2329 return;
2330 }
2331 }
2332 }
2333 }
2334 } else {
2335 *text_plain_ct = reply_ct;
2336 }
2337 }
2338
2339 reply_ct->c_cefile.ce_file = reply_file;
2340 reply_ct->c_cefile.ce_fp = reply_fp;
2341 reply_ct->c_cefile.ce_unlink = 1;
2342
2343 /* Attach the new part to the parent multipart/mixed, "m". */
2344 NEW0(part);
2345 part->mp_part = reply_ct;
2346 if (m->mp_parts) {
2347 struct part *p;
2348
2349 for (p = m->mp_parts; p && p->mp_next; p = p->mp_next) { continue; }
2350 p->mp_next = part;
2351 } else {
2352 m->mp_parts = part;
2353 }
2354 }
2355
2356
2357 /* Extract any Content-Type header from beginning of convert output. */
2358 int
2359 extract_headers (CT ct, char *reply_file, FILE **reply_fp)
2360 {
2361 char *buffer = NULL, *cp, *end_of_header;
2362 bool found_header = false;
2363 struct stat statbuf;
2364
2365 /* Read the convert reply from the file to memory. */
2366 if (stat (reply_file, &statbuf) == NOTOK) {
2367 admonish (reply_file, "failed to stat");
2368 goto failed_to_extract_ct;
2369 }
2370
2371 buffer = mh_xmalloc (statbuf.st_size + 1);
2372
2373 if ((*reply_fp = fopen (reply_file, "r+")) == NULL ||
2374 fread (buffer, 1, (size_t) statbuf.st_size, *reply_fp) <
2375 (size_t) statbuf.st_size) {
2376 admonish (reply_file, "failed to read");
2377 goto failed_to_extract_ct;
2378 }
2379 buffer[statbuf.st_size] = '\0';
2380
2381 /* Look for a header in the convert reply. */
2382 if (strncasecmp (buffer, TYPE_FIELD, LEN(TYPE_FIELD)) == 0 &&
2383 buffer[LEN(TYPE_FIELD)] == ':') {
2384 if ((end_of_header = strstr (buffer, "\r\n\r\n"))) {
2385 end_of_header += 2;
2386 found_header = true;
2387 } else if ((end_of_header = strstr (buffer, "\n\n"))) {
2388 ++end_of_header;
2389 found_header = true;
2390 }
2391 }
2392
2393 if (found_header) {
2394 CT tmp_ct;
2395 char *tmp_file;
2396 FILE *tmp_f;
2397 size_t n, written;
2398
2399 /* Truncate buffer to just the C-T. */
2400 *end_of_header = '\0';
2401 n = strlen (buffer);
2402
2403 if (get_ctinfo (buffer + 14, ct, 0) != OK) {
2404 inform("unable to get content info for reply, continuing...");
2405 goto failed_to_extract_ct;
2406 }
2407
2408 /* Hack. Use parse_mime() to detect the type/subtype of the
2409 reply, which we'll use below. */
2410 tmp_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2411 tmp_f = fopen(tmp_file, "w");
2412 if (!tmp_f)
2413 goto failed_to_extract_ct;
2414 written = fwrite(buffer, 1, n, tmp_f);
2415 fclose(tmp_f);
2416 if (written != n)
2417 goto failed_to_extract_ct;
2418
2419 tmp_ct = parse_mime (tmp_file);
2420 if (tmp_ct) {
2421 /* The type and subtype were detected from the reply
2422 using parse_mime() above. */
2423 ct->c_type = tmp_ct->c_type;
2424 ct->c_subtype = tmp_ct->c_subtype;
2425 free_content (tmp_ct);
2426 }
2427
2428 free (tmp_file);
2429
2430 /* Rewrite the content without the header. */
2431 cp = end_of_header + 1;
2432 rewind (*reply_fp);
2433
2434 if (fwrite (cp, 1, statbuf.st_size - (cp - buffer), *reply_fp) <
2435 (size_t) (statbuf.st_size - (cp - buffer))) {
2436 admonish (reply_file, "failed to write");
2437 goto failed_to_extract_ct;
2438 }
2439
2440 if (ftruncate (fileno (*reply_fp), statbuf.st_size - (cp - buffer)) !=
2441 0) {
2442 advise (reply_file, "ftruncate");
2443 goto failed_to_extract_ct;
2444 }
2445 } else {
2446 /* No header section, assume the reply is text/plain. */
2447 ct->c_type = CT_TEXT;
2448 ct->c_subtype = TEXT_PLAIN;
2449 if (get_ctinfo ("text/plain", ct, 0) == NOTOK) {
2450 /* This never should fail, but just in case. */
2451 die("unable to get content info for reply");
2452 }
2453 }
2454
2455 /* free_encoding() will close reply_fp, which is passed through
2456 ct->c_cefile.ce_fp. */
2457 free (buffer);
2458 return OK;
2459
2460 failed_to_extract_ct:
2461 if (*reply_fp) { fclose (*reply_fp); }
2462 free (buffer);
2463 return NOTOK;
2464 }