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