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