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